提供 MySQL 数据库架构验证、迁移决策审查、性能影响评估及安全合规检查。包含迁移前检查清单、SQL 规范、备份策略、数据一致性验证及常见反模式识别。
本技能帮助您在数据库设计、迁移或重大变更时,系统地验证架构合理性、评估迁移风险、确保数据完整性,并遵循最佳实践。适用于开发阶段的设计评审、上线前的迁移审核,以及日常运维中的决策支持。
| 概念 | 说明 | 关键检查点 |
|---|---|---|
| 范式 | 减少冗余与异常 | 3NF 通常足够,适当反范式用于性能 |
| 索引 | 加速查询,减慢写入 | 覆盖索引、最左前缀、索引选择性 |
| 外键约束 | 保证引用完整性 | 注意锁和级联操作的影响 |
| 事务隔离级别 | 控制并发行为 | RR(默认)或 RC,注意幻读、间隙锁 |
| 迁移策略 | 变更不中断服务 | pt-online-schema-change, gh-ost |
| 备份类型 |
| 逻辑备份(mysqldump) vs 物理备份(XtraBackup) |
| RPO/RTO 要求 |
| 复制延迟 | 主从同步滞后 | 监控 Seconds_Behind_Master |
| 数据一致性 | 迁移前后数据相同 | 校验和、行数对比、抽样验证 |
在开始任何数据库迁移前,请逐项确认以下内容:
ALTER TABLE ... ALGORITHM=INPLACE, LOCK=NONE 检查可行性| 工具/方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 原生 ONLINE DDL | MySQL 5.6+,添加列/索引,修改默认值 | 无需额外工具,语法简单 | 大表仍可能短暂锁表,不支持删除列等重写操作 |
| pt-online-schema-change | 大表(>10GB),避免阻塞写入 | 触发器方式,几乎无锁,可暂停 | 需要额外权限,触发器影响性能 |
| gh-ost | 大表,要求无触发器,更安全 | 无触发器,基于 binlog 回放,可测试 | 配置稍复杂,需要 binlog 格式 ROW |
| 手动影子表 | 完全控制迁移过程 | 灵活,适用于复杂转换 | 代码量大,容易出错 |
pt-online-schema-change \
--alter "ADD INDEX idx_user_status (user_id, status)" \
D=yourdb,t=orders \
--host=127.0.0.1 \
--user=admin \
--password=*** \
--chunk-size=1000 \
--critical-load="Threads_running=200" \
--max-load="Threads_running=50" \
--execute
EXPLAIN 分析,关注 type(ALL 代表全表扫描)、key、rows、Extra(Using filesort / Using temporary 应避免)WHERE DATE(created_at) = '2024-01-01' 无法使用索引,应改为 created_at BETWEEN ...WHERE id = '123'(id 为 INT)会导致全表扫描CHECK 保证字段值范围-- 1. 记录表行数、数据大小
SELECT
table_name,
table_rows,
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb
FROM information_schema.tables
WHERE table_schema = 'yourdb' AND table_name = 'target_table';
-- 2. 关键列的数据分布(用于迁移后对比)
SELECT
COUNT(*) AS total,
SUM(IF(status = 'active', 1, 0)) AS active_count,
SUM(IF(status = 'inactive', 1, 0)) AS inactive_count,
MIN(created_at) AS min_created,
MAX(created_at) AS max_created
FROM target_table;
-- 3. 外键依赖检查
SELECT
CONSTRAINT_NAME,
TABLE_NAME,
COLUMN_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_NAME = 'target_table' AND TABLE_SCHEMA = 'yourdb';
-- 4. 存在重复数据(若计划添加唯一索引)
SELECT user_id, order_id, COUNT(*)
FROM target_table
GROUP BY user_id, order_id
HAVING COUNT(*) > 1;
-- 1. 对比行数(应与迁移前一致)
SELECT COUNT(*) FROM target_table;
-- 2. 关键列校验和(使用 CRC32 或 MD5 聚合)
SELECT
COUNT(*) AS total,
SUM(IF(status = 'active', 1, 0)) AS active_count,
SUM(IF(status = 'inactive', 1, 0)) AS inactive_count,
MIN(created_at) AS min_created,
MAX(created_at) AS max_created
FROM target_table;
-- 3. 检查新增列默认值是否正确填充(对于 NOT NULL DEFAULT ...)
SELECT COUNT(*) FROM target_table WHERE new_column IS NULL; -- 应为 0
-- 4. 测试关键查询性能(对比 EXPLAIN 输出)
EXPLAIN SELECT * FROM target_table WHERE user_id = 123 AND status = 'active';
#!/bin/bash
# 使用 mysqldump 对比迁移前后两个数据库实例的表数据
mysqldump -h source_host -u user -p'pass' yourdb target_table --no-create-info --skip-extended-insert > /tmp/old_data.sql
mysqldump -h target_host -u user -p'pass' yourdb target_table --no-create-info --skip-extended-insert > /tmp/new_data.sql
diff /tmp/old_data.sql /tmp/new_data.sql
if [ $? -eq 0 ]; then
echo "数据完全一致"
else
echo "数据存在差异,请手动检查"
fi
mysqlbinlog 提取反向 SQL-- 迁移前
RENAME TABLE orders TO orders_old, orders_new TO orders;
-- 如果迁移失败,反向重命名
mysql -h target -u user -p yourdb < backup.sql
#!/bin/bash
# 每日验证备份可恢复性
BACKUP_FILE="/backup/mysql/$(date +%Y%m%d)_yourdb.sql.gz"
TEST_DB="test_restore_$(date +%Y%m%d%H%M%S)"
gunzip -c $BACKUP_FILE | mysql -e "CREATE DATABASE $TEST_DB; USE $TEST_DB; source /dev/stdin"
# 简单检查是否有表
TABLE_COUNT=$(mysql -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$TEST_DB'")
if [ $TABLE_COUNT -gt 0 ]; then
echo "备份验证成功,共 $TABLE_COUNT 张表"
mysql -e "DROP DATABASE $TEST_DB"
else
echo "备份验证失败"
exit 1
fi
| 场景 | 推荐方案 | 注意事项 |
|---|---|---|
| 添加列(允许 NULL) | ALTER TABLE ADD COLUMN (ALGORITHM=INPLACE) | 几乎无影响,可在线执行 |
| 添加列(NOT NULL 无默认值) | 先添加允许 NULL,再分批更新值,最后 MODIFY NOT NULL | 避免大表锁 |
| 修改列数据类型(INT→BIGINT) | ALTER TABLE MODIFY,需重建表 | 大表使用 pt-osc |
| 删除索引 | ALTER TABLE DROP INDEX (INPLACE) | 立即生效,但需确认查询性能 |
| 重命名表 | 原子操作,但需更新所有引用 | 应用需配合停机或新老表双写 |
| 字符集转换(latin1→utf8mb4) | 使用 pt-osc,并注意索引长度限制 | 可能增加存储空间 |
| 合并分区 | 先创建新表,INSERT SELECT,然后重命名 | 停机时间长,建议在线工具 |
| 数据清理(删除过期行) | 分批删除,避免大事务 | 控制每批 1000 行,加 LIMIT |
迁移前与迁移后,对比以下指标:
SHOW GLOBAL STATUS 或监控系统)SHOW SLAVE STATUS\G 中的 Seconds_Behind_Master)SHOW ENGINE INNODB STATUS)SELECT data_length, index_length ...)# 安装 sysbench,准备数据
sysbench oltp_read_write --mysql-host=localhost --mysql-db=testdb --tables=10 --table-size=100000 prepare
# 迁移前测试
sysbench oltp_read_write --threads=16 --time=60 run > before.txt
# 执行迁移
# 迁移后测试
sysbench oltp_read_write --threads=16 --time=60 run > after.txt
# 对比 before.txt 和 after.txt 中的 transactions per second
require_secure_transport=ON)openssl enc 加密备份文件| 问题 | 诊断命令 | 解决方案 |
|---|---|---|
| 迁移期间锁等待 | SHOW PROCESSLIST; 查看 State | 使用在线工具或低峰期,KILL 阻塞查询 |
| 复制延迟飙升 | SHOW SLAVE STATUS\G 看 Seconds_Behind_Master | 调整 slave_parallel_workers,或使用 pt-slave-restart 跳过临时错误 |
| 磁盘空间不足 | df -h,检查 binlog 和 ibdata1 | 清理旧 binlog(PURGE BINARY LOGS),或添加磁盘 |
| 外键约束失败 | SHOW ENGINE INNODB STATUS 查看最新外键错误 | 检查数据完整性,暂时禁用外键检查 SET foreign_key_checks=0(谨慎) |
| 应用连接失败 | 检查 max_connections 和 threads_connected | 增加 max_connections,或排查连接泄漏 |
| 数据类型转换错误 | 查看 MySQL 错误日志 | 使用 CAST 或修改应用逻辑,确保数据可转换 |