深度复盘|数据库迁移实战(下):从自建 MySQL 5.7 到腾讯云 MySQL 8.0 的 SQL 兼容改造

深度复盘|数据库迁移实战(下):从自建 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 新增了窗口函数保留字,rankgroupssystem 等变成关键字。

改写前

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 里的 utf8utf8mb3 的别名,最多 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';

踩坑 1CONVERT 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 ONlong_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 兼容扫描思路,评论区交流。

相关推荐
2501_930472441 小时前
深度复盘|数据库迁移实战(上):腾讯云助手解析慢查询日志,定位索引缺失与语法不兼容
数据库·阿里云·ffmpeg·云计算·腾讯云·aws
SelectDB技术团队1 小时前
从 ClickHouse 迁移到 Doris:SQL 兼容、同步与验证清单
数据库·人工智能·sql·clickhouse·apache doris·selectdb·湖仓架构升级
leo_messi941 小时前
Mysql学习(十二) -- SQL执行到底做了什么事?
sql·学习·mysql
敲代码的嘎仔1 小时前
从零实现视频续播 + 学习进度统计:前端心跳、条件更新、GROUP BY 统计全链路拆解
java·前端·数据库·学习·面试·职场和发展·音视频
草莓熊Lotso1 小时前
【Redis 初阶】C++ 客户端实战:从 RESP 协议到 redis-plus-plus 工程化用法
linux·开发语言·网络·数据库·c++·redis·缓存
dunge20261 小时前
2026年9月11日|ChatGPT Pro + Codex:GPT‑6 Astra 数据库性能优化
数据库·gpt·chatgpt
NeilYuen2 小时前
【kv存储】结合faiss构建向量内存数据库
数据库·faiss
风哥2号2 小时前
数据库教程FGMT18‑Oracle‑EMCC智能化集中运维管理系统
运维·数据库·oracle
Zixhy4 小时前
手撕Reactor模型实现同步高并发服务器及Muduo网络库深入解析
linux·服务器·网络·数据库·c++