4. MySQL 数据库部署:MySQL 8.0 镜像拉取、数据持久化、主从复制与支付核心表设计
4.1 MySQL 8.0 Docker 镜像拉取与容器启动
支付系统对数据库的稳定性和性能要求极高,MySQL 8.0 是目前生产环境的主流选择。使用 Docker 部署可以快速实现环境标准化和版本一致性。
4.1.1 镜像拉取与版本确认
# 拉取 MySQL 8.0 官方镜像(默认使用最新 8.0.x 稳定版)
docker pull mysql:8.0
# 验证镜像已拉取
docker images | grep mysql
4.1.2 启动容器并验证运行状态
# 启动一个测试容器(非持久化,仅用于验证)
docker run --name mysql-test \
-e MYSQL_ROOT_PASSWORD=PayRoot@2024 \
-d mysql:8.0
# 检查容器是否正常运行
docker ps | grep mysql-test
# 进入容器验证 MySQL 版本
docker exec -it mysql-test mysql -uroot -p'PayRoot@2024' -e "SELECT VERSION();"
版本选择建议:生产环境建议锁定具体小版本号(如
mysql:8.0.32),避免因镜像标签更新导致意外升级。支付系统需经过严格回归测试后再升级数据库版本。
4.2 数据持久化配置
容器默认存储是临时的,容器删除后数据会丢失。支付系统的交易数据必须通过 数据卷(Volume) 或 主机绑定挂载(Bind Mount) 实现持久化。
4.2.1 使用 Docker Volume 持久化(推荐)
# 创建数据卷和配置卷
docker volume create mysql-data
docker volume create mysql-conf
docker volume create mysql-logs
# 启动持久化容器
docker run --name mysql-pay \
-e MYSQL_ROOT_PASSWORD=PayRoot@2024 \
-e MYSQL_DATABASE=payment_db \
-e MYSQL_USER=payuser \
-e MYSQL_PASSWORD=PayUser@2024 \
-v mysql-data:/var/lib/mysql \
-v mysql-conf:/etc/mysql/conf.d \
-v mysql-logs:/var/log/mysql \
-p 3306:3306 \
-d mysql:8.0
4.2.2 关键目录映射说明
| 容器内路径 | 作用 | 持久化建议 |
|---|---|---|
/var/lib/mysql |
数据库数据文件(ibd、frm、undo log、redo log) | 必须持久化,使用 Volume 或 Bind Mount |
/etc/mysql/conf.d |
自定义配置文件(my.cnf 片段) | 强烈建议持久化,便于调优参数 |
/var/log/mysql |
错误日志、慢查询日志、通用日志 | 建议持久化,用于故障排查和审计 |
/var/lib/mysql-files |
LOAD DATA INFILE 临时文件目录 | 按需持久化,支付场景较少使用 |
4.2.3 生产环境 my.cnf 配置示例
[mysqld]
# 字符集与排序规则
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# InnoDB 引擎优化
innodb_buffer_pool_size = 4G
innodb_log_file_size = 1G
innodb_flush_log_at_trx_commit = 1
innodb_file_per_table = 1
# 连接与线程
max_connections = 500
thread_cache_size = 64
# 二进制日志(主从复制必须开启)
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
expire_logs_days = 7
# 慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
支付系统关键参数:
innodb_flush_log_at_trx_commit = 1 确保每次事务提交都刷新日志到磁盘,虽然会降低写入性能,但这是支付数据完整性的底线要求。切勿为了性能牺牲这个参数。
4.3 主从复制架构搭建
支付系统必须保证数据库高可用,主从复制是基础架构。MySQL 8.0 支持异步复制、半同步复制和组复制(Group Replication)。生产环境推荐使用 半同步复制 或 组复制 来保证数据一致性。
4.3.1 主从复制架构图
4.3.2 主库配置步骤
- 创建复制用户
-- 在主库执行
CREATE USER 'repl'@'%' IDENTIFIED BY 'Repl@2024';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
- 获取主库 Binlog 位置
-- 锁定表并查看当前 Binlog 文件名和位置
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;
-- 记录 File 和 Position 值,例如:mysql-bin.000003, 157
-- 解锁(导出数据后执行)
UNLOCK TABLES;
- 导出主库数据并导入从库
# 在主库导出全量数据
docker exec mysql-pay mysqldump -uroot -p'PayRoot@2024' \
--all-databases --master-data=2 --single-transaction \
> /backup/master_dump.sql
# 将 dump 文件复制到从库容器
docker cp /backup/master_dump.sql mysql-slave1:/tmp/
# 在从库导入数据
docker exec mysql-slave1 mysql -uroot -p'PayRoot@2024' < /tmp/master_dump.sql
4.3.3 从库配置与启动复制
-- 在从库执行
CHANGE MASTER TO
MASTER_HOST='192.168.1.100', -- 主库 IP
MASTER_PORT=3306,
MASTER_USER='repl',
MASTER_PASSWORD='Repl@2024',
MASTER_LOG_FILE='mysql-bin.000003',
MASTER_LOG_POS=157;
-- 启动复制线程
START SLAVE;
-- 检查复制状态
SHOW SLAVE STATUS\G;
-- 关键字段:Slave_IO_Running: Yes, Slave_SQL_Running: Yes
4.3.4 复制状态监控清单
| 监控指标 | 正常值 | 异常处理 |
|---|---|---|
Slave_IO_Running |
Yes | 检查网络连通性、复制用户权限、主库 Binlog 是否被清理 |
Slave_SQL_Running |
Yes | 检查 SQL 线程错误(如主键冲突、表不存在),使用 SET GLOBAL sql_slave_skip_counter = 1 跳过(谨慎) |
Seconds_Behind_Master |
0 或接近 0 | 延迟过大时检查从库硬件性能、网络带宽、大事务 |
Last_IO_Errno / Last_SQL_Errno |
0 | 非零值表示复制中断,需根据错误码排查 |
支付系统复制策略:建议采用 一主两从 或 一主三从 架构。其中一个从库开启
log_slave_updates 作为级联复制的中间节点,另一个从库作为只读备份节点。所有从库都开启 read_only = 1 防止误写入。
4.4 支付核心表结构设计原则
支付系统的表结构设计直接关系到资金安全、对账效率和查询性能。以下是经过生产验证的核心设计原则。
4.4.1 支付核心表设计原则总览
4.4.2 支付订单表示例(核心表)
CREATE TABLE `payment_order` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`order_id` varchar(32) NOT NULL COMMENT '业务订单号(全局唯一)',
`user_id` bigint NOT NULL COMMENT '用户ID(分片键)',
`merchant_id` varchar(16) NOT NULL COMMENT '商户编号',
`product_name` varchar(128) DEFAULT NULL COMMENT '商品名称',
`total_amount` decimal(18,2) NOT NULL COMMENT '订单总金额(单位:元)',
`pay_amount` decimal(18,2) NOT NULL COMMENT '实际支付金额',
`currency` varchar(3) NOT NULL DEFAULT 'CNY' COMMENT '币种',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '支付状态:0-待支付 1-支付中 2-支付成功 3-支付失败 4-退款中 5-已退款',
`pay_channel` varchar(16) DEFAULT NULL COMMENT '支付渠道:wechat/alipay/unionpay',
`channel_order_no` varchar(64) DEFAULT NULL COMMENT '渠道订单号',
`notify_status` tinyint DEFAULT '0' COMMENT '回调通知状态:0-未通知 1-通知成功 2-通知失败',
`expire_time` datetime DEFAULT NULL COMMENT '订单过期时间',
`pay_time` datetime DEFAULT NULL COMMENT '支付完成时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_id` (`order_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_merchant_id` (`merchant_id`),
KEY `idx_status_create_time` (`status`, `create_time`),
KEY `idx_channel_order_no` (`channel_order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付订单表';
4.4.3 支付流水表(不可变审计)
CREATE TABLE `payment_transaction` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`transaction_id` varchar(32) NOT NULL COMMENT '交易流水号(全局唯一)',
`order_id` varchar(32) NOT NULL COMMENT '关联订单号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`trans_type` tinyint NOT NULL COMMENT '交易类型:1-支付 2-退款 3-撤销 4-冲正',
`amount` decimal(18,2) NOT NULL COMMENT '交易金额',
`before_balance` decimal(18,2) DEFAULT NULL COMMENT '交易前余额',
`after_balance` decimal(18,2) DEFAULT NULL COMMENT '交易后余额',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '交易状态:0-处理中 1-成功 2-失败',
`request_no` varchar(64) DEFAULT NULL COMMENT '请求流水号(幂等键)',
`channel_response` text COMMENT '渠道返回原始报文',
`error_code` varchar(32) DEFAULT NULL COMMENT '错误码',
`error_msg` varchar(256) DEFAULT NULL COMMENT '错误信息',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_transaction_id` (`transaction_id`),
UNIQUE KEY `uk_request_no` (`request_no`),
KEY `idx_order_id` (`order_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付交易流水表(不可变审计日志)';
不可变设计要点:支付流水表 禁止 UPDATE 和 DELETE 操作。所有状态变更通过新增流水记录实现。例如支付失败后重试,会新增一条状态为“成功”的流水,而不是修改原失败记录。这样保证了完整的审计轨迹,便于对账和问题追溯。
4.4.4 对账结果表
CREATE TABLE `reconciliation_result` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`recon_date` date NOT NULL COMMENT '对账日期',
`order_id` varchar(32) NOT NULL COMMENT '订单号',
`transaction_id` varchar(32) NOT NULL COMMENT '交易流水号',
`channel` varchar(16) NOT NULL COMMENT '渠道',
`our_amount` decimal(18,2) NOT NULL COMMENT '我方金额',
`channel_amount` decimal(18,2) NOT NULL COMMENT '渠道金额',
`diff_amount` decimal(18,2) DEFAULT '0.00' COMMENT '差异金额',
`recon_status` tinyint NOT NULL DEFAULT '0' COMMENT '对账状态:0-未对账 1-一致 2-长款 3-短款 4-异常',
`handle_status` tinyint DEFAULT '0' COMMENT '处理状态:0-未处理 1-已处理',
`remark` varchar(256) DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_recon_date` (`recon_date`),
KEY `idx_order_id` (`order_id`),
KEY `idx_recon_status` (`recon_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对账结果表';
4.4.5 表设计避坑清单
| 常见错误 | 后果 | 正确做法 |
|---|---|---|
| 金额字段使用 FLOAT/DOUBLE | 浮点精度丢失,导致资金误差 | 使用 DECIMAL(18,2) 或 BIGINT(单位:分) |
| 订单号使用自增 ID | 暴露业务量,分库分表后全局唯一性难保证 | 使用雪花算法或 UUID 生成全局唯一订单号 |
| 状态字段使用 VARCHAR | 查询性能差,无法使用枚举约束 | 使用 TINYINT 枚举,代码中定义常量 |
| 缺少幂等键(request_no) | 重复回调导致重复入账 | 每个请求携带唯一幂等键,建唯一索引 |
| 不分区大表 | 单表过大,查询和维护困难 | 按时间分区(RANGE)或按用户哈希分表 |
资金安全红线:支付订单表和流水表的所有金额字段必须使用
DECIMAL(18,2),禁止使用 FLOAT/DOUBLE。涉及跨境支付的场景,建议使用 DECIMAL(18,4) 保留更高精度。所有金额计算必须在应用层使用高精度计算库(如 Java 的 BigDecimal),禁止在数据库层面做浮点运算。