深度复盘|数据库迁移实战(下):从自建 MySQL 5.7 到腾讯云 MySQL 8.0 的 SQL 兼容改造
分类:数据库 / MySQL
标签:MySQL、数据库迁移、腾讯云、DTS、SQL兼容、DBA、后端
摘要:性能问题可以慢慢优化,语法不兼容会直接卡死迁移窗口。本文复盘从自建 MySQL 5.7 迁到腾讯云 MySQL 8.0 时遇到的 9 类 SQL 兼容问题,给出改写前后对照、参数组配置、DTS 迁移流程与双跑对账方案。
一、迁移的三个阶段,难点在第二阶段
text
阶段一:结构与数据迁移(DTS)------ 有工具,最"确定"
阶段二:SQL 兼容改造 ------ 最难,会阻塞上线
阶段三:灰度与对账 ------ 最需要纪律
阶段一用 DTS 基本能自动化。真正让项目延期的是阶段二:应用里有 SQL 在 5.7 上跑得好好的,到 8.0 直接报错,而且往往是凌晨切流量时才暴露。
所以我们的策略是:在迁移窗口前,用静态分析把所有兼容问题一次性扫出来。
二、第一步:拿到全量 SQL
应用是 Java 的,SQL 散在 MyBatis XML、注解和代码拼接里。
bash
# 1) MyBatis XML:直接抓 SQL 语句
grep -rhoP '(?s)<(select|insert|update|delete)\b.*?</\1>' \
--include='*.xml' src/ > sql_from_xml.txt
# 2) 注解式 SQL
grep -rhoP '@(Select|Insert|Update|Delete)\s*\(\s*"[^"]+"' \
--include='*.java' src/ > sql_from_java.txt
# 3) 更可靠的做法:从生产环境采集(覆盖拼接 SQL)
# 打开 general log 采样 30 分钟,或在 8.0 测试库上开
# performance_schema.events_statements_history_long
mysql -e "SELECT SQL_TEXT FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC LIMIT 5000" > sql_from_prod.txt
第 3 种最可靠 :代码里的 SQL 和实际执行的 SQL 往往不一致(动态拼接、ORM 生成、存储过程)。我们的实际比例是代码里 892 条,生产实际执行 2137 个 digest,漏了 1245 个。
三、第二步:源码扫描,找出兼容风险点
3.1 提示词
text
背景:我要把自建 MySQL 5.7 迁移到腾讯云 MySQL 8.0。
下面是应用里的全量 SQL(约 2100 条,已去重)。
请逐条检查以下 8.0 兼容性问题,并输出表格:
1. ONLY_FULL_GROUP_BY:SELECT 列表含非聚合列但未在 GROUP BY 中
2. 保留字冲突:rank, groups, system, cume_dist, dense_rank, first_value,
lag, lead, last_value, nth_value, ntile, over, window, percent_rank,
row_number, recursive, of, empty, group, json_table
3. 零值日期:'0000-00-00' 相关写入(NO_ZERO_DATE / NO_ZERO_IN_DATE)
4. utf8mb3 弃用:utf8 / utf8mb3 字符集声明
5. 已删除函数/语法:PROCEDURE ANALYSE、SELECT ... INTO 多变量、
@@GLOBAL 变量拼写、\N 用法、密码函数 PASSWORD()
6. INTEGER 显示宽度:int(11)、bigint(20) 等已弃用写法
7. GROUP BY 隐式排序的依赖:依赖 5.7 GROUP BY 自带排序的 SQL
8. ASC/DESC 混用:8.0 真降序索引与 5.7 静默忽略行为差异
输出字段:文件/来源、SQL 片段、问题类型、是否阻断(阻断/告警)、
改写后 SQL、改写说明。
要求:如果不确定是否兼容,标注"需人工确认",不要猜测。
3.2 扫描结果汇总
| 问题类型 | 命中条数 | 是否阻断 |
|---|---|---|
ONLY_FULL_GROUP_BY |
87 | 阻断 |
| 保留字冲突 | 34 | 阻断 |
| 零值日期 | 19 | 阻断 |
utf8mb3 声明 |
213 | 告警(迁移时统一改 utf8mb4) |
| 已删除语法 | 3 | 阻断 |
| 整型显示宽度 | 640 | 告警(无害,但建议清理) |
| 依赖 GROUP BY 隐式排序 | 12 | 逻辑错误(不报错但结果错) |
DESC 索引语义变化 |
5 | 告警 |
最危险的是最后一类:不报错,但结果不对。这类问题的排查成本远高于直接报错。
四、9 类兼容问题与改写方案
问题 1:ONLY_FULL_GROUP_BY(87 条,最高频)
MySQL 5.7 默认已开 ONLY_FULL_GROUP_BY,但很多自建实例被 DBA 手工关掉了。我们的源库就是关掉的。目标库是腾讯云 MySQL 默认参数组,无法关闭(实际上 8.0 也不建议关)。
改写前:
sql
-- 反例:GROUP BY 只有 category_id,但 SELECT 了非聚合列 title、create_time
SELECT category_id, title, create_time, COUNT(*) AS cnt
FROM cms_article
GROUP BY category_id;
5.7 关闭 ONLY_FULL_GROUP_BY 时,MySQL 会自动取"每组任意一行"的值------结果是不确定的。
改写方案 A:明确语义(推荐) ------ 用窗口函数取真正想要的记录:
sql
-- 取每个分类下最新的那篇文章
SELECT category_id, title, create_time, cnt
FROM (
SELECT category_id, title, create_time,
ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY create_time DESC) AS rn,
COUNT(*) OVER (PARTITION BY category_id) AS cnt
FROM cms_article
) t
WHERE rn = 1;
改写方案 B:把非聚合列加入 GROUP BY(当业务上确实要按这些列分组时):
sql
SELECT category_id, title, create_time, COUNT(*) AS cnt
FROM cms_article
GROUP BY category_id, title, create_time;
关键决策点 :必须问业务方"原来的 SQL 到底想要哪一行" 。不能简单用 ANY_VALUE() 糊过去:
sql
-- 反例:这就等于把 5.7 的不确定行为搬到了 8.0,问题被掩盖
SELECT category_id, ANY_VALUE(title), COUNT(*) FROM cms_article GROUP BY category_id;
我们 87 条里,有 31 条属于"业务方自己也说不清要哪一行"------这 31 条是潜在的存量 bug,借迁移机会修掉了。
问题 2:保留字冲突(34 条)
8.0 新增了窗口函数保留字,rank、groups、system 等变成关键字。
改写前:
sql
SELECT id, rank, groups FROM biz_level WHERE system = 1;
text
ERROR 1064 (42000): You have an error in your SQL syntax ... near 'rank, groups FROM'
改写方案:反引号包裹(最小改动,推荐):
sql
SELECT id, `rank`, `groups` FROM biz_level WHERE `system` = 1;
更彻底的方案:改列名。我们最终改了,因为这类列名会让所有后续开发都难受:
sql
ALTER TABLE biz_level CHANGE COLUMN `rank` level_rank INT NOT NULL COMMENT '等级序号';
ALTER TABLE biz_level CHANGE COLUMN `groups` user_groups VARCHAR(64);
ALTER TABLE biz_level CHANGE COLUMN `system` is_system TINYINT NOT NULL DEFAULT 0;
排查方法(提前发现,不靠运行时报错):
bash
# 检查现有列名/表名是否撞上 8.0 保留字
mysql -N -B -e "SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA='biz'" > columns.txt
# 8.0 新增关键字清单
cat > keywords.txt <<'EOF'
CUME_DIST DENSE_RANK EMPTY EXCEPT FIRST_VALUE GROUPS GROUPING JSON_TABLE
LAG LAST_VALUE LATERAL LEAD NTH_VALUE NTILE OF OVER PERCENT_RANK RANK
RECURSIVE ROW_NUMBER SYSTEM WINDOW
EOF
awk '{print $2}' columns.txt | tr 'A-Z' 'a-z' | sort -u > col_names.txt
comm -12 col_names.txt keywords.txt
问题 3:零值日期(19 条)
改写前:
sql
INSERT INTO biz_task (id, task_name, expire_at) VALUES (1, 'test', '0000-00-00 00:00:00');
8.0 严格模式下报错:
text
ERROR 1292 (22007): Incorrect datetime value: '0000-00-00 00:00:00' for column 'expire_at'
改写方案:改语义,而不是改参数 。用 NULL 表达"未设置",这才是正确建模:
sql
-- 表结构
ALTER TABLE biz_task MODIFY COLUMN expire_at DATETIME NULL DEFAULT NULL
COMMENT '过期时间,NULL 表示永不过期';
-- 写入
INSERT INTO biz_task (id, task_name, expire_at) VALUES (1, 'test', NULL);
-- 查询(兼容老逻辑的 NULL 处理)
SELECT id, task_name FROM biz_task
WHERE expire_at IS NULL OR expire_at > NOW();
反例(不要这样) :把目标库 sql_mode 里的 NO_ZERO_DATE 去掉。腾讯云 MySQL 参数组虽可调整,但掩盖问题会让应用继续依赖非法数据,一旦将来做 DTS 增量同步或备份恢复都会出问题。
问题 4:字符集 utf8mb3 → utf8mb4(213 处)
关键认知 :MySQL 里的 utf8 是 utf8mb3 的别名,最多 3 字节,存不了 emoji 和部分生僻字 。8.0 中 utf8mb3 已标记弃用,未来会移除。
排查:
sql
SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'biz'
AND CHARACTER_SET_NAME IN ('utf8', 'utf8mb3');
统一方案(迁移时一次性做完):
sql
-- 1) 连接的字符集(应用侧)
-- jdbc:mysql://...?characterEncoding=utf8mb4&connectionCollation=utf8mb4_general_ci
-- 注意:characterEncoding=UTF-8 在 Connector/J 8.x 下等价于 utf8mb4,但显式写更清楚
-- 2) 库
ALTER DATABASE biz CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
-- 3) 表(批量生成)
SELECT CONCAT('ALTER TABLE `', TABLE_NAME, '` CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;')
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'biz' AND TABLE_TYPE = 'BASE TABLE';
踩坑 1 :CONVERT TO CHARACTER SET 会重建表并可能改变索引长度 。utf8mb4 下每字符最多 4 字节,VARCHAR(255) 索引会从 765 字节涨到 1020 字节。InnoDB 默认 DYNAMIC 行格式单列索引限 3072 字节,一般没问题;但如果是 COMPACT 格式(限 767 字节),必须改行格式:
sql
ALTER TABLE biz_article ROW_FORMAT=DYNAMIC;
踩坑 2:排序规则不统一会导致 JOIN 时索引失效:
sql
-- 一个是 utf8mb4_general_ci,一个是 utf8mb4_0900_ai_ci → 隐式转换,索引失效
-- 统一到同一个 collation,不要混用
问题 5:已删除语法(3 条)
sql
-- 5.7 可用,8.0 已移除
SELECT * FROM biz_order PROCEDURE ANALYSE();
-- ERROR 1064
-- 改写:用统计查询替代
SELECT COUNT(*) AS total, COUNT(DISTINCT status) AS status_distinct,
MIN(id) AS min_id, MAX(id) AS max_id FROM biz_order;
sql
-- 5.7 的 \N 在 LOAD DATA 中表示 NULL,8.0 行为变化
LOAD DATA INFILE 'x.csv' INTO TABLE t FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n';
-- 改写:显式声明
LOAD DATA INFILE 'x.csv' INTO TABLE t
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
(col1, col2, @v3) SET col3 = NULLIF(@v3, '\\N');
问题 6:整型显示宽度(640 条,非阻断)
sql
-- 5.7 常见写法
CREATE TABLE t (id INT(11) NOT NULL AUTO_INCREMENT, big BIGINT(20));
-- 8.0 弃用 INT(11) 显示宽度(对 TINYINT 的 zerofill 除外)
这不是报错项 ,但会在 SHOW CREATE TABLE 时被截断,导致结构比对工具误报 diff。建议一次性清理:
bash
# 批量生成 DDL 清理语句(去掉显示宽度)
# 或直接用 mysqldump --compatible 输出后人工核对
注意 :ZEROFILL 属性依赖显示宽度,8.0 中仍保留但已弃用。如果用了 INT(11) ZEROFILL,需要把显示逻辑移到应用层。
问题 7:依赖 GROUP BY 隐式排序(12 条,最危险)
这是最需要警惕的一类:不报错,但结果错。
MySQL 5.7 中 GROUP BY 会隐式排序(8.0 已移除该行为,不再保证顺序)。
改写前:
sql
-- 5.7 上:结果按 category_id 升序返回
SELECT category_id, COUNT(*) FROM cms_article GROUP BY category_id;
-- 8.0 上:顺序不确定!应用依赖了这个顺序
改写方案:显式声明排序:
sql
SELECT category_id, COUNT(*) FROM cms_article
GROUP BY category_id
ORDER BY category_id; -- 显式,8.0 上语义稳定
排查方法:
bash
# 找出没有 ORDER BY 的 GROUP BY 语句
grep -rniE "group\s+by" sql_all.txt | grep -viE "order\s+by" > group_no_order.txt
我们把这 12 条逐一让业务方确认"是否依赖返回顺序",其中 4 条确实依赖(前端做下拉列表默认顺序),补上 ORDER BY 后修复。
问题 8:DESC 索引语义变化(5 条)
5.7 中 INDEX (col DESC) 的 DESC 被静默忽略,总是升序。8.0 支持真降序索引。
sql
-- 5.7:DESC 被忽略,实际是 (create_time ASC)
-- 8.0:真是 (create_time DESC)
ALTER TABLE biz_order ADD INDEX idx_time (create_time DESC);
影响 :如果原来依赖索引做 ORDER BY create_time ASC,8.0 上这个降序索引反而用不上 → 性能回退。
方案:迁移后验证执行计划。
sql
EXPLAIN SELECT id FROM biz_order ORDER BY create_time ASC LIMIT 10;
-- 检查 key 是否用上、Extra 是否有 Using filesort
-- 如果没有用上,补一个 ASC 索引或去掉 DESC
问题 9:TIMESTAMP 与 DATETIME 的默认值差异
sql
-- 5.7 的 explicit_defaults_for_timestamp=OFF 下,一个表里第一个 TIMESTAMP 列
-- 不写默认值会自动变成 NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-- 8.0 默认 explicit_defaults_for_timestamp=ON,不再有隐式行为
排查:
sql
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE,
COLUMN_DEFAULT, EXTRA
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'biz' AND DATA_TYPE = 'timestamp'
ORDER BY TABLE_NAME, ORDINAL_POSITION;
建议:统一显式声明,不要依赖隐式行为:
sql
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
五、改写前后对照总表
| # | 问题 | 改写前 | 改写后 | 风险 |
|---|---|---|---|---|
| 1 | ONLY_FULL_GROUP_BY | GROUP BY category_id 却 SELECT title |
窗口函数 ROW_NUMBER() 或补全 GROUP BY |
阻断 |
| 2 | 保留字 | SELECT rank, groups |
SELECT `rank`, `groups` |
阻断 |
| 3 | 零值日期 | '0000-00-00' |
NULL + IS NULL 判断 |
阻断 |
| 4 | utf8mb3 | DEFAULT CHARSET=utf8 |
utf8mb4 + 统一 collation |
告警 |
| 5 | 删除语法 | PROCEDURE ANALYSE() |
独立统计查询 | 阻断 |
| 6 | 显示宽度 | INT(11) |
INT |
告警 |
| 7 | GROUP BY 隐式排序 | 无 ORDER BY |
显式 ORDER BY |
静默错误 |
| 8 | DESC 索引 | (col DESC) |
按实际查询方向建索引 | 性能回退 |
| 9 | TIMESTAMP 隐式默认 | 不声明默认值 | 显式 DEFAULT CURRENT_TIMESTAMP |
静默行为变更 |
六、目标库准备:参数组与账号
6.1 参数组设置(腾讯云 MySQL 控制台)
不要关掉严格模式,而是对齐源库语义、逐项确认:
| 参数 | 建议值 | 说明 |
|---|---|---|
sql_mode |
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
8.0 推荐值,不要为了兼容而关闭 |
character_set_server |
utf8mb4 |
与服务端一致 |
collation_server |
utf8mb4_general_ci |
与源库保持一致,避免 JOIN 索引失效 |
lower_case_table_names |
与源库一致 | 此项必须在初始化时设置,创建实例后不可改 |
max_connections |
按实际峰值 × 1.5 | 从源库 SHOW STATUS LIKE 'Max_used_connections' 取参考 |
innodb_buffer_pool_size |
规格内存的 60%~75% | 云上按规格自动给,一般无需改 |
slow_query_log |
ON,long_query_time=1 |
迁移后持续观察,与迁移前日志对比 |
lower_case_table_names是迁移最容易翻车的参数 :Linux 自建库通常是0(区分大小写),如果新建实例时忘了设,之后无法修改,只能重建实例。我们在压测环境就踩过这个坑。
6.2 账号与权限
sql
-- 应用账号:最小权限,不要用 root
CREATE USER 'biz_app'@'%' IDENTIFIED BY '<strong-password>';
GRANT SELECT, INSERT, UPDATE, DELETE ON biz.* TO 'biz_app'@'%';
-- 迁移账号(DTS 用):需要更宽权限
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'dts_user'@'%';
-- 只读对账账号:仅 SELECT,用于双跑校验
CREATE USER 'biz_check'@'%' IDENTIFIED BY '<password>';
GRANT SELECT ON biz.* TO 'biz_check'@'%';
七、灰度与对账:迁移后的三道验证
7.1 第一道:SQL 能否执行
迁移后先做全量 SQL 回放 ,把生产采集的 2137 个 digest 在测试库上跑一遍(EXPLAIN 即可,不真正执行 DML):
bash
#!/bin/bash
# replay_check.sh ------ 全量 SQL 兼容性回放
set -uo pipefail
TARGET_HOST="mysql-target.internal"
FAIL_FILE="replay_fail.txt"
: > "${FAIL_FILE}"
total=0; fail=0
while IFS= read -r sql; do
[ -z "${sql}" ] && continue
total=$((total + 1))
if ! mysql -h"${TARGET_HOST}" -ubiz_check -p"${CHECK_PASS}" -N -B \
-e "EXPLAIN ${sql}" >/dev/null 2>"/tmp/err.txt"; then
fail=$((fail + 1))
{
echo "--- SQL #${total} ---"
echo "${sql}"
cat /tmp/err.txt
} >> "${FAIL_FILE}"
fi
done < sql_from_prod.txt
echo "总计 ${total} 条,失败 ${fail} 条,详见 ${FAIL_FILE}"
这一步的价值:把"凌晨切流量时才发现报错"变成"白天提前发现"。
7.2 第二道:双跑对账
DTS 做增量同步期间,用只读账号在源库和目标库跑同一批查询,比对结果摘要:
python
#!/usr/bin/env python3
"""
dual_run_check.py ------ 双跑对账
对关键查询在源库与目标库同时执行,比对结果哈希
"""
import hashlib
import pymysql
SRC = dict(host='mysql-src.internal', user='biz_check', password='***', database='biz')
DST = dict(host='mysql-target.internal', user='biz_check', password='***', database='biz')
CHECKS = [
("订单总数", "SELECT COUNT(*) FROM biz_order"),
("订单金额合计", "SELECT ROUND(SUM(amount),2) FROM biz_order"),
("用户数", "SELECT COUNT(*) FROM sys_user"),
("近7日订单分组", """
SELECT DATE(created_at) d, COUNT(*) c FROM biz_order
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY DATE(created_at) ORDER BY d"""),
("订单主键校验和", "SELECT BIT_XOR(CRC32(CONCAT_WS('#', id, order_no, amount))) FROM biz_order"),
]
def run(conn_info, sql):
conn = pymysql.connect(**conn_info, charset='utf8mb4')
try:
with conn.cursor() as cur:
cur.execute(sql)
rows = cur.fetchall()
return hashlib.md5(repr(rows).encode()).hexdigest(), rows
finally:
conn.close()
def main():
all_pass = True
print(f"{'检查项':<16}{'源库':<12}{'目标库':<12}{'结果'}")
print("-" * 56)
for name, sql in CHECKS:
h_src, r_src = run(SRC, sql)
h_dst, r_dst = run(DST, sql)
same = h_src == h_dst
all_pass &= same
print(f"{name:<16}{h_src[:8]:<12}{h_dst[:8]:<12}{'PASS' if same else 'FAIL'}")
if not same:
print(f" 源库: {r_src[:5]}")
print(f" 目标库: {r_dst[:5]}")
print("-" * 56)
print("总体:", "全部一致" if all_pass else "存在差异,需人工确认")
return 0 if all_pass else 1
if __name__ == '__main__':
raise SystemExit(main())
注意 :增量同步期间两边数据本就在变化,对账要用同一时间点的快照语义 。我们的做法是:先在源库执行 SELECT ... FOR SHARE 拿快照 ID,或在业务低峰期暂停写入 5 分钟再对账。
7.3 第三道:读流量灰度切流
text
第 1 步:全部读走源库,目标库只做健康检查 ------ 校验连通
第 2 步:5% 读流量走目标库(按用户 ID 取模) ------ 观察错误率、P99
第 3 步:50% 读流量走目标库 ------ 观察 30 分钟
第 4 步:100% 读流量走目标库,源库保持增量同步(可回退) ------ 观察 2 小时
第 5 步:确认无误后,写流量切换 + 停止 DTS ------ 不可逆点
第 4 步是关键安全网 :此时写仍在源库,DTS 在同步,任何问题都能秒回滚到源库。第 5 步之后才需要真正的勇气,所以前面所有验证都是为了这一步。
八、时间线与复盘
| 阶段 | 耗时 | 关键动作 |
|---|---|---|
| SQL 采集 | 3 天 | 代码扫描 + 生产 digest 采集 |
| 静态分析 | 2 天 | 9 类兼容问题扫描 + 出清单 |
| SQL 改写 | 8 天 | 87 条 GROUP BY 是主要工作量(含业务确认) |
| 目标库准备 | 4 天 | 参数组、账号、字符集统一 |
| SQL 回放验证 | 1 天 | 2137 条 digest 全量 EXPLAIN |
| DTS 全量 + 增量 | 1 天 / 持续 | --- |
| 双跑对账 | 2 天 | 5 类关键指标 |
| 灰度切流 | 1 天 | 5% → 50% → 100% |
| 合计 | 约 3 周 | --- |
三条最重要的经验
1. SQL 采集不要只扫代码,一定要从生产采集。
我们代码里 892 条,生产实际 2137 个 digest。如果只扫代码,会漏掉 58% 的 SQL,其中包括 3 条会直接报错的保留字查询。
2. ONLY_FULL_GROUP_BY 和"GROUP BY 隐式排序"是同一件事的两面。
前者报错,后者静默出错。修订前一定要问业务方"原来那行到底要哪一行",不能 ANY_VALUE() 糊过去。我们因此发现并修复了 31 个存量数据逻辑 bug------这是迁移的意外收益。
3. 不要为了迁移成功而降低目标库的严格性。
关掉 ONLY_FULL_GROUP_BY、去掉 NO_ZERO_DATE 能让迁移"更顺",但代价是把技术债带到新环境,而且下次再迁(比如上云原生数据库)时还要还一遍。迁移是清理技术债最合适的时机,因为业务方此时最有动力配合。
九、配套阅读
- 上一篇:《深度复盘|数据库迁移实战(上):腾讯云助手解析慢查询日志,定位索引缺失与语法不兼容》------性能问题定位(48213 条慢查询 → 6 类问题模式)
- 相关:《上云迁移验收:CVM 健康校验用例 + 数据库迁移比对报告》------把本文的对账方案与 CVM 校验整合成一份可交付的验收报告
如果这篇文章对你有帮助,欢迎点赞收藏。DBA 同行如果有更好的 SQL 兼容扫描思路,评论区交流。
gest。如果只扫代码,会漏掉 58% 的 SQL,其中包括 3 条会直接报错的保留字查询。
2. ONLY_FULL_GROUP_BY 和"GROUP BY 隐式排序"是同一件事的两面。
前者报错,后者静默出错。修订前一定要问业务方"原来那行到底要哪一行",不能 ANY_VALUE() 糊过去。我们因此发现并修复了 31 个存量数据逻辑 bug------这是迁移的意外收益。
3. 不要为了迁移成功而降低目标库的严格性。
关掉 ONLY_FULL_GROUP_BY、去掉 NO_ZERO_DATE 能让迁移"更顺",但代价是把技术债带到新环境,而且下次再迁(比如上云原生数据库)时还要还一遍。迁移是清理技术债最合适的时机,因为业务方此时最有动力配合。
九、配套阅读
- 上一篇:《深度复盘|数据库迁移实战(上):腾讯云助手解析慢查询日志,定位索引缺失与语法不兼容》------性能问题定位(48213 条慢查询 → 6 类问题模式)
- 相关:《上云迁移验收:CVM 健康校验用例 + 数据库迁移比对报告》------把本文的对账方案与 CVM 校验整合成一份可交付的验收报告
如果这篇文章对你有帮助,欢迎点赞收藏。DBA 同行如果有更好的 SQL 兼容扫描思路,评论区交流。