记一次大数据量同步导致的 SQL Server 崩溃与全线服务掉线复盘
Contents
0. 背景
最近在进行项目迁移,需要将数据 from PostgreSQL 同步到 SQL Server。为了快速实现逻辑,编写了一个 Python 脚本 sync_pg_to_mssql.py,利用 Pandas 的 read_sql_query 配合 to_sql 进行分块同步。
然而,在一次针对包含千万级数据(尤其是含有 JSON 字段)的大表进行同步时,服务器突然“失联”,引发了一场连锁反应。
1. 问题表现
在执行同步脚本约 10 分钟后,出现了以下异常:
- 服务全线掉线:Cloudflare Tunnel、frpc 和 Tailscale 等远程访问服务几乎同时失去连接。
- SSH 响应极慢:原本流畅的 SSH 终端开始出现数秒甚至数十秒的延迟。
- 监控中断:所有运行在宿主机上的监控进程停止更新。
- 容器重启:SQL Server 容器在崩溃后触发了自动重启。
2. 排查思路 & 经过
2.1 现场初步分析
通过内网环境(SSH -p 6000)勉强登录后,发现系统负载(Load Average)已经飙升到了一个恐怖的数值。查看 SQL Server 容器日志,发现了关键报错:
| |
这表明系统不仅内存耗尽,磁盘 IO 也陷入了严重的阻塞。
2.2 sar 数据分析
由于系统在崩溃期间几乎无法操作,我们查阅了 sysstat 的历史记录(sar)。
在崩溃前的最后一秒(10:09:01 PM),数据如下:
- 内存使用率:97.09%,Swap 占用:97.35%。系统已经进入了 Thrashing(抖动) 状态。
- IO 等待 (%iowait):87.13%。这意味着 CPU 绝大部分时间都在等待磁盘写入完成。
- 负载 (Load Average):474.52(在 20 核机器上,这意味着近 450 个进程在排队)。
2.3 根因定位
- SQL Server 内存过度预留:SQL Server 在 Linux 容器下默认会尝试预留尽可能多的物理内存。在没有配置
MSSQL_MEMORY_LIMIT_MB的情况下,它与宿主机系统发生了激烈的内存抢占。 - Pandas 内存峰值:脚本中
chunksize=50000对于含有大量 JSON 字段的表来说过大。在处理json.dumps时,内存中产生了海量的字符串对象。 - IO 饱和与事务日志爆炸:由于表数据量巨大(单表最高 58GB),
to_sql产生的海量同步 IO 彻底瘫痪了磁盘系统。 - OOM Killer 介入:当内存和 Swap 同时耗尽,内核触发 OOM Killer 杀掉了占用资源或优先级较低的进程(包括 frpc 等隧道服务),导致全线掉线。
3. 反思与改进
3.1 容器化不代表可以忽视资源限制
在生产环境中,永远不要在不设限的情况下运行 SQL Server 容器。
- 必须设置
MSSQL_MEMORY_LIMIT_MB环境参数。 - 在 Docker Compose 中应使用
deploy.resources.limits明确限制内存。
3.2 代码层面的优化
- 减小分块大小:针对宽表或含有 LOB/JSON 字段的表,应显著调小
chunksize。 - 使用更高效的同步方式:对于 10GB 以上的表,应优先考虑
BCP或BULK INSERT,而不是通过 Pandas 中转。 - 开启驱动级优化:例如
fast_executemany=True以减少往返次数。
3.3 系统级监控预警
- Swappiness 调整:在高 IO 数据库场景下,应适当降低宿主机的 swappiness 值。
- IO 等待告警:当
%iowait持续超过 20% 时应立即触发预警。
这次事故再次提醒我们:大数据量的同步不仅仅是代码逻辑问题,更是一场关于 CPU、内存与磁盘 IO 的资源博弈。
Author
LastMod 2026-05-27