大规模数据导出任务之所以容易打满网卡并拖垮在线查询,根本原因在于导出过程占满了网络出口带宽,导致查询请求的数据包在队列里干等;解决方案就是给导出任务做限速、分流或走旁路快照,让在线查询永远有路可走。
最近不少运维朋友跟我聊到一个共性难题:半夜跑一个几千万行的表导出,第二天一看监控,网卡流量直接打满,在线业务查询延迟从十几毫秒飙到两三秒,甚至直接超时,这事情吧,说白了不是数据库扛不住,而是网卡带宽被导出任务一个人全吃光了,无论你用的是MySQL、PostgreSQL还是Oracle,只要是大表全量导出,几乎都会踩这个坑。
数据库导出影响线上业务怎么办:先确认是不是导出任务抢了带宽
遇到线上查询变慢,第一反应别急着骂数据库,也别急着重启,你先把网卡流量拉出来看一眼,大概率会看到一张“平躺”的流量曲线持续跑满在千兆或者万兆口上,而在线查询虽然SQL本身很快,但结果集在网络层就开始排队,数据库再快也白搭。
如何判断导出任务正在打满网卡(MySQL大表导出场景)
在Linux服务器上,你可以用三步快速定位:
- 敲
iftop或nload,直接看实时网卡流量,如果出口带宽占比持续超过80%,而平时这个时间点流量很闲,嫌疑就落在定时导出任务上。 - 执行
ss -antp | grep :3306(换成你自己的数据库端口),看看是不是有大量来自导出机或跳板机的长连接存在,而且连接数还特别多。 - 到数据库里查
SHOW PROCESSLIST;,如果看到SELECT FROM 大表 INTO OUTFILE或者类似全表扫描但无WHERE条件的查询,且跑了很久,那基本就实锤了。
说个典型场景:凌晨2点,杭州某电商公司的数据仓库做每日全量导出,备份机直连生产库拉一张8000万行的订单表,最开始是千兆内网,单线程导,带宽还能凑合;后来表涨到1.2亿行,导出一启动,千兆口直接被占满,业务侧第二天一早的报表查询全部超时,后来加了SELECT ... INTO OUTFILE配合gzip压缩,流量虽然降了,但网卡仍然经常跑满,这类问题,无论是内部BI取数、第三方数据同步还是ETL任务,只要直连生产库大量拖数据,就一定会和在线查询抢带宽。
导出数据会不会影响线上查询从网络链路到数据库内核的完整拆解
行业共识认为,导出一张大数据表对数据库本身的CPU和磁盘影响往往不是主要矛盾,真正的瓶颈在“数据库发数据”到“客户端收数据”这条链路上,这里面有几个容易被忽略的细节。
为什么数据导出比在线查询更容易占满网卡
在线查询大多数带有WHERE条件,返回的行数有限,哪怕扫描全表也只是扫描索引或缓存,结果集小,数据导出则相反,它是把整张表的行数据一股脑往外搬,有几个显著特点:
- 单条数据量大:一行订单数据可能包含几十个字段,动辄几百字节到几千字节;哪怕每秒只发1万行,也能轻松吃掉数百Mb带宽。
- 连接不缓存、不限制速率:默认的MySQL客户端或
mysqldump都没有内置限速逻辑,只要网卡能发多快就发多快,直到把出口带宽填满。 - 网络栈浅队列:网卡缓冲区只有那么深,数据包到网卡后发不出去就排队,排队的包里有大量在线查询的响应包,它们只能眼巴巴等着。
在线查询变慢的真正原因在于排队延迟而非处理延迟
很多人有个误解,以为数据库因为忙着导出所以“没空”处理查询,MySQL或PostgreSQL的并发处理能力远没脆弱到被一个导出连接卡死,真正的问题出现在数据库侧发送数据包到交换机,再返回客户端的这个环节。
举个例子:你往一个装满水的水管里注水(导出任务),此时在线查询的小船(响应包)明明只有一小条,但你得混在这股湍急的水流里慢慢挤过去,延迟就上来了。
解决导出影响在线查询的问题,只有两条路:让导出任务的流量降速,或者让导出任务的流量走别的路。
数据导出限速实操:用Linux TC和iptables标记给导出任务单独划条道
如果你不想改代码、不想动数据库,最快速止血的办法就是在操作系统层面给导出任务的TCP流量做限速,这个方法对MySQL、PostgreSQL、MongoDB都有效,而且能精确到IP、端口、用户甚至SQL语句。
用tc命令按端口限速(以MySQL 3306为例)
下面是一套使用tc(Traffic Control)配合iptables对MySQL从库拉取或导出流量做限速的命令,操作路径是Linux 7/8/9全系通用:
# 1. 创建根队列规则,设置出口带宽上限(假设网卡eth0,限制200Mbps) tc qdisc add dev eth0 root handle 1: htb default 30 tc class add dev eth0 parent 1: classid 1:1 htb rate 200mbit burst 15k # 2. 创建专门给导出连接用的子类(带宽上限100Mbps) tc class add dev eth0 parent 1:1 classid 1:10 htb rate 100mbit ceil 150mbit # 3. 用iptables给目标端口为3306的包打上标记(mark=10) iptables -t mangle -A OUTPUT -p tcp --dport 3306 -j MARK --set-mark 10 # 4. 让标记为10的流量走这个受限子类 tc filter add dev eth0 parent 1: protocol ip prio 1 handle 10 fw classid 1:10
等导出任务跑完,想要恢复设置,可以执行tc qdisc del dev eth0 root和iptables -t mangle -F OUTPUT进行清理,这套方案能做到“导出任我行,查询慢不了”。
针对特定IP的限速命令
如果你知道导出固定来自某台机器(比如IP168.10.50),可以更精准地按源地址限流:
iptables -t mangle -A OUTPUT -d 192.168.10.50 -j MARK --set-mark 20
再仿照上面流程,给标记20单独分配一个限速子类,就只限制流向这台机器的流量,在线查询完全不受影响,统计数据表明,这种方法实施后,多数做过的运维反馈导出速度只下降不到30%(其实导出的瓶颈往往不在带宽而在磁盘读),但业务查询的延迟基本恢复原样。
彻底根治法:给大规模导出任务单独配置一张网卡
限速是治标,而对于频繁做全量导出的场景,最好给数据库服务器插上第二块物理网卡,专用于数据分发,这项操作在机架式服务器、刀片机和云主机的物理网卡场景里都适用,但对云主机而言需要在控制台额外挂一张虚拟网卡。
一张独立网卡的配置流程
物理上插好网卡后,做以下配置(以Linux为例):
- 用
ip addr查看新网卡名称,比如eth1。 - 编辑网卡配置文件,设置独立网段IP,比如
/etc/sysconfig/network-scripts/ifcfg-eth1:DEVICE=eth1 BOOTPROTO=static IPADDR=10.0.2.10 NETMASK=255.255.255.0 ONBOOT=yes - 重启网络服务或执行
ifup eth1激活。 - 在应用层配置数据库服务器的
bind-address,或者让同步工具直接把目标地址指向0.2.10。
这样导出流量只走eth1,在线查询流量走eth0,两条链路物理隔离、互不干扰,根据实际经验,即便导出机器数量较多,使用这张网卡也能在导出持续期间保证原有在线查询的平均延迟基本持平,数据库服务器网卡的整体丢包率控制在零附近,成本方面,一张双口千兆的企业级PCIe网卡市场价约几百元,对比业务受损造成的损失,这笔账非常划算。
基于策略路由的精细分流配置
只有一张网卡、一个IP的情况下,还可以用Linux策略路由做到“源端口分流”,也就是,来自3306端口的数据包走默认网关,来自导出专用端口(比如3307)的数据包走另一个网关,操作办法是:
ip rule add from 10.0.2.10 lookup 100 ip route add default via 192.168.1.1 dev eth0 table 100
这条命令的意思是:凡是出口源IP为0.2.10(即导出专用IP)的数据包单独查路由表100,走预先指定的网关,这样导出的流量就从物理路径上和在线查询分开了。
大规模数据导出的预防性设计:快照导出成为最优解法
实话说,限速和分流都只是让导出跑得慢一点、绕一点,但真正优雅的方案是做快照导出,快照导出的核心思想是:不再直连生产数据库拉数据,而是先对数据文件做一次轻量级快照(或复制备份),然后在副本上做全量导出,业内专家指出,这些年做得好的企业多数已经全面切到快照导出模式。
用存储层快照替代SELECT 导出
以常见的云数据库(简米云RDS或酷番云)为例,操作路径是:
- 在控制台点击“创建临时实例”或“只读实例”,通常有“临时查询”功能,账单按小时计。
- 在临时实例上执行
mysqldump导出,生产实例的网络完全不受影响。 - 导完直接释放临时实例,按量计费的开销在合理范围内(比如一次500GB库、跑几小时,花费约数十元,对于业务来说可以接受)。
如果自建机房,则用LVM快照或ZFS快照,先创建一致性快照,再挂载到另一台机器上导出,快照导出还有额外的好处:
导出期间数据库的buffer pool和磁盘读几乎不增压力.
错峰导出与限流组合拳
不差时间但差带宽的场景,出表时间可以调整到业务低谷,比如凌晨4点到6点,再把mysqldump的--single-transaction参数配合--net-buffer-length调小,比如设置成64KB或更小的16KB,每个TCP包都短一些,这样单个连接产生的突发流量也就更低,多个导出任务之间最好再串行跑,避免出现两个大导出同时挤带宽的情况,运维角度,可以设置crontab只在周日凌晨执行,或者在接受的数据同步工具(如DataX、Kettle)里配好限速参数。
纯查询场景:逻辑导出后用分流网关承载流量
企业内部BI报表、多维分析平台取数,建议数据先导到独立BI库/数仓(ClickHouse、Doris等),再由业务方连数仓,在数仓出口再做一次限速和ACL隔离,查询响应速度和导出安稳,两边都不亏。
数据库导出任务影响线上查询的Q&A
导出数据量不大但网卡还是被打满,可能是什么原因?
网卡被打满的原因有时不在数据量大小,而在于连接数太多,如果用了并发度很高的工具(DataX、mysqldump多线程),同时开启32个连接,即使单条数据量很小,每个连接都快速发送小包,TCP的PUSH+ACK交互频率会急剧增加,网卡小包处理能力先被打满(常见于千兆网卡),建议先检查是不是并发度过高,适当降低--parallel或channel参数。
用简米云RDS做mysqldump导出时,为什么限速不生效?
简米云RDS是托管实例,用户的ECS无法直接对RDS的网卡做tc限速,因为tc必须在源端施行,操作思路改为:在目标ECS的出口侧对来自RDS的流量限速,具体做法是在ECS上执行iptables规则,把来源于RDS内网IP的包标记后走一个低速htb队列,如果不想这么麻烦,可以直接在RDS控制台创建一个只读实例,查询和导出都走只读实例。
导出任务跑完后,在线查询还是慢,怎么排查?
导出任务停止但查询仍慢,就要考虑是不是数据库侧的资源已经受影响,比如导出时把buffer pool里的热数据挤了出去,后续查询大量走磁盘,也有可能是导出机把数据库连接占满了,导致新连接被拒绝或排队,先看SHOW GLOBAL STATUS LIKE 'Threads_connected';,如果连接数接近max_connections,就清理闲置连接或调大max_connections,再看SHOW ENGINE INNODB STATUS;检查磁盘读情况,若服务器同时还在做全量备份,慢查询会雪上加霜。
说到底,数据导出这类批量任务本质上不是数据库问题,而是网络资源调度问题,优先采取独立网卡或快照导出的方案,其次用tc做限速兜底,查询的命就不会被导出给“卡脖子”,实践验证过的项目,导出照跑,查询延迟照低,两全其美。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/639437.html





