找回密码
立即注册
搜索
发回帖 发新帖

5003

积分

0

好友

649

主题
发表于 1 小时前 | 查看: 4| 回复: 0

上线前一天,测试环境一切正常。

数据迁移脚本也就一条 SQL。我那个同事看了一眼,觉得问题不大。

insert into user_archive
select *
from user_info
where create_time < '2024-01-01';

几百万条历史数据,从业务表迁移到归档表。执行时间也不算长,测试环境跑完,数据对得上,然后他很开心地提了上线。

结果第二天生产直接炸了。接口大量超时,数据库 CPU 拉满,部分业务写入失败。最后处理结果也很严重——因为这次事故影响比较大,他离开了公司。

事情过去以后,我复盘了一遍这个问题。很多人看到 insert into select,第一反应是:“这不就是复制数据吗?”但在线上环境,这条 SQL 的杀伤力,比很多复杂代码还大。

先看一下这个 SQL 到底干了什么。假设现在有一张订单表:

create table order_info (
    id bigint primary key,
    user_id bigint,
    amount decimal(10,2),
    status varchar(20),
    create_time datetime
);

归档表:

create table order_history (
    id bigint primary key,
    user_id bigint,
    amount decimal(10,2),
    status varchar(20),
    create_time datetime
);

迁移:

insert into order_history
select *
from order_info
where create_time < '2024-01-01';

看起来没毛病。但是数据库执行的时候,并不是简单复制文件。它会:

  1. 扫描 order_info
  2. 根据条件过滤数据
  3. 读取每一行数据
  4. 生成 insert 操作
  5. 写入目标表
  6. 记录 redo log / binlog

数据量小的时候,你感觉不到。数据量一上来,问题全部暴露。

第一个坑:一次性大事务

很多同学不知道,insert into select 默认就是一个事务。比如:

insert into order_history
select *
from order_info
where create_time < '2024-01-01';

假设迁移 500 万数据,数据库需要保证:要么 500 万全部成功,要么全部失败回滚。这意味着事务期间:

  • undo log 增长
  • redo log 增长
  • 锁持有时间增加
  • binlog 增长

线上最怕这种一次性大事务。我之前排查过一次类似问题,监控看到:

Threads_running: 85

History list length: 1289234

Innodb_buffer_pool_pages_dirty: 92%

业务接口开始大量超时。很多人第一眼会看接口代码,我一般先看数据库。因为这种指标组合,非常像有人在数据库里干了“大动作”。

查当前事务:

select
    trx_id,
    trx_started,
    trx_mysql_thread_id,
    trx_query
from information_schema.innodb_trx;

果然看到:

trx_id: 883921233
trx_started: 2026-09-28 10:22:11

trx_query:
insert into order_history
select ...

一个迁移任务跑了二十多分钟还没结束,业务线程全部陪它等。

第二个坑:执行计划根本没看

很多人写迁移 SQL,只看结果,不看它怎么执行。比如:

insert into user_archive
select *
from user_info
where user_id in (
    select user_id
    from user_temp
);

测试环境数据少,跑得飞快;生产环境直接慢。原因很简单:生产表几千万数据,如果索引不合适,可能变成全表扫描。

上线前至少应该看:

explain
select *
from user_info
where create_time < '2024-01-01';

比如看到:

type: ALL

rows: 38000000

Extra: Using where

这种结果,我基本不会让它直接在线上跑。3800 万行扫描,你觉得它只是复制数据,数据库觉得:“终于有人让我干一次全库体检了。”

第三个坑:影响线上写入

很多迁移任务喜欢晚上执行,觉得晚上没人用。但现在很多互联网业务不是这样。晚上可能还有:

  • 定时任务
  • 数据同步
  • 清算任务
  • 报表任务

数据库资源就那么多,迁移占 CPU,业务写入也需要 CPU;迁移占 IO,业务查询也需要 IO。最后大家一起排队。

那这种数据迁移应该怎么写?我个人习惯不会直接:

insert into archive
select *
from source;

而是拆。比如按照主键分批,Java 里一般会写一个小脚本控制。类似这样:

public void migrateOrder() {

    long lastId = 0;
    int batchSize = 5000;

    while (true) {

        List<OrderDTO> list =
            orderDao.queryAfterId(lastId, batchSize);

        if (list.isEmpty()) {
            break;
        }

        orderArchiveDao.batchInsert(list);

        lastId = list.get(list.size() - 1).getId();

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
        }
    }
}

这里几个点比较重要。

不要分页:

limit 500000,5000

这种越往后越慢,因为数据库需要扫描前面的数据。用:

where id > lastId
order by id
limit 5000

效率稳定很多。

另外批量插入也不要无限大。比如:

jdbcTemplate.batchUpdate(
    "insert into order_history(id,user_id,amount,status) values(?,?,?,?)",
    batchArgs
);

一次塞几十万条,一样危险。我一般控制在几千,具体多少看:

  • 单条数据大小
  • 网络情况
  • redo log 压力
  • 数据库配置

不要迷信某个固定数字。

还有一个容易忽略的问题:迁移一定要有校验,不是插进去就结束。比如源表:

select count(*)
from order_info
where create_time < '2024-01-01';

目标表:

select count(*)
from order_history
where create_time < '2024-01-01';

数量一致只是第一步。还应该抽样:

select id, amount, status
from order_info
where id in (10001,10002,10003);

对比归档表。否则最怕数据迁移成功了,但是字段错位,业务几年以后才发现。

最后说一句,insert into select 这条 SQL 本身没有错。很多线上事故,也不是因为某个人 SQL 写错了。问题在于:开发环境给了错误信号。几万条数据没问题,几千万条数据就是另外一个世界。

数据库不像 Java 方法,你调用一次,看见返回结果,就觉得完成了。数据库背后还有:事务日志、锁、磁盘、缓存、复制。所有这些东西,都会因为你这一条 SQL 被调动起来。

所以以后看到这种:

insert into xxx
select *
from xxx

别急着说简单。先问三个问题:数据多少?执行计划怎么样?出了问题怎么停?这三个问题没想明白,迁移脚本就不要直接上生产。




上一篇:减少网页卡顿:浏览器长任务拆分与主线程让步策略
下一篇:王兴兴谈具身智能:最大错误是对AI不够相信,一两年内或迎临界点
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-14 08:12 , Processed in 0.093438 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表