
做 TiDB 部署和调优这些年我养成一个习惯遇到集群性能不对劲先不急着看 SQL 慢日志也不急着调 TiKV 的 RocksDB 参数而是先翻一遍操作系统参数。尤其是你刚装完一台 64G 内存、NVMe 固态的 TiKV 节点压测时延迟却忽高忽低CPU 占用也不规律这种时候八成不是数据库配置的问题而是内核参数还在用默认值扛着数据库负载。TiDB 是分布式架构底层依赖操作系统提供的文件、内存、网络、进程调度能力这部分短板不补上层再优化也像在沙地上盖楼。这篇文章我直接把“tidb-操作系统参数优化”这件事拆开讲清楚。我会按照内存与 CPU、磁盘与文件系统、网络协议栈、配置落地与验证、问题排查五个层面来梳理全部给到可以直接抄的配置值和验证命令。无论你是在测试环境做功能验证还是在生产环境准备正式上线这部分内容都值得你对着实际操作一遍。1. 先弄明白TiDB 到底对操作系统敏感在哪1.1 三种节点角色对系统资源的需求完全不同很多人一上来就照着网上找的“通用高并发服务器优化”一通乱改这是最容易踩坑的开端。TiDB 集群里有三种角色TiDB Server 负责 SQL 解析和计算下推对 CPU 和网络延迟敏感TiKV 负责实际数据存储对磁盘 IOPS、内存分配、文件句柄数极其敏感PD 负责全局时间戳和调度对磁盘延迟和时钟稳定性敏感。三种角色混用一套系统参数必然顾此失彼。举个例子TiKV 底层用 RocksDB 做存储引擎RocksDB 重度依赖操作系统的 page cache、文件系统 writeback 机制以及内存映射效率。如果系统的 vm.swappiness 保持默认值 60当内存使用率波动时内核会倾向把 TiKV 的部分热数据页换到 swap 分区上一次换入换出的时间消耗可能是几毫秒甚至几十毫秒对 TiKV 这种延迟敏感的存储节点就是灾难。而同样的问题在 TiDB Server 节点上就没那么致命因为 TiDB Server 本身不落盘主要消耗的是计算和网络资源。所以在做系统参数优化之前首先要明确一个原则针对不同的节点角色做差异化配置。存储节点优先考虑磁盘和内存回收行为计算节点优先考虑 CPU 调度和网络栈吞吐PD 节点优先考虑磁盘延迟和文件系统挂载方式。不要一套配置走天下。这里我给的建议是生产环境至少准备两套模板一套面向 TiKV/PD一套面向 TiDB Server如果条件允许PD 单独再调细一点。1.2 为什么默认内核参数扛不住 TiDB 负载Linux 默认内核参数是面向通用服务器和应用场景设计的。默认情况下系统不会为一类数据库预留大量文件句柄也不会把磁盘 I/O 调度器设置成延迟优先模式更不会特意扩大 TCP 连接队列。这些默认值对应的是“通用的 web 服务器、文件服务器、离线计算任务”等常规负载它们的特点是吞吐量优先、容忍延迟抖动、连接数相对可控。但 TiDB 是另一类负载。TiKV 的 Raft log 写入是强同步的写入路径上一个 fsync 的延迟波动会直接反映到事务提交耗时上TiKV 之间的 gRPC 通信有频繁的短连接和长连接混合流量网络栈处理能力不足会出现连接建立慢、 Waiting 队列堆积TiDB 的每个 SQL 查询都会产生大量临时文件和内存映射文件描述符上限不够时错误日志里会出现 too many open files 之类的问题。这些都不是 TiDB 自身参数能解决的只有操作系统层面把它当作数据库服务器来专门配置才能把底层能力释放出来。另一个容易被忽略的点是TiDB 集群通常部署在云虚拟机或物理机的数据盘上这些盘的性能已经非常强但内核参数如果不配合比如磁盘预读策略和脏页回写比例设置不合适再强的硬件也发挥不出应有性能。这就好比给赛车配了顶级轮胎但悬挂系统还是轿车设定过弯照样慢。2. 内存与 CPUTiKV 延迟问题的第一来源2.1 vm.swappiness到底设 0 还是 1说到 TiKV 节点调优vm.swappiness 是绕不开的第一个参数。这个参数控制内核在使用匿名内存时有多大的倾向性去把内存页换到 swap。默认值是 60含义是内核会相对积极地使用 swap 来释放内存页缓存。对大多数应用来说这个值没问题但对 TiKV 来说问题大了TiKV 的 block cache 和 RocksDB 的 memtable 都是热数据被换出到 swap 后再读回来代价非常高。关于这个参数有两个流派一派主张直接设 0彻底禁用 swap 倾向另一派主张设 1保留极小的 swap 行为以便应对极端内存压力。从 TiDB 官方文档和社区实践来看普遍建议是设 1。原因很简单设 0 意味着内核在内存压力极大时宁可 OOM kill 也不会换页而 TiKV 这种大内存应用一旦触发 OOM后果是整个 Raft group 的副本短暂掉线多副本场景下虽然能恢复但瞬间的抖动对上层业务是有感知的。设 1 则保留了一个非常保守的兜底空间在极端情况下内核能缓慢回收 swap 中的冷页而不是直接杀进程。实操命令sysctl -w vm.swappiness1 echo vm.swappiness 1 /etc/sysctl.d/99-tidb.conf顺便说一句很多人在云主机上根本没有配置 swap 分区这种情况下设 0 和设 1 其实差别不大但为了统一配置管理和可预期性建议还是统一设 1 并保留一个 swap 文件兜底容量不用大几个 G 就够。2.2 透明大页必须关CPU 调频模式必须切到性能优先透明大页Transparent Huge PagesTHP是 Linux 内核的一个特性它试图自动把连续的内存页合并成 2MB 的大页从而减少 TLB miss。听起来很美但在数据库场景下它经常成为性能杀手。原因是 THP 的内存分配和合并过程需要后台内核线程做内存规整memory compaction这个过程会触发大量的 CPU 占用和锁竞争导致分配延迟剧烈抖动。RocksDB 对内存分配延迟极其敏感一块内存分配的抖动就可能让 Raft 的心跳超时。关闭 THP 有两种方式一是运行时直接修改 sysfs 接口二是通过内核启动参数彻底禁用。推荐两种都做因为有些发行版即使修改了 sysfs重启后会被系统服务重新开启。echo never /sys/kernel/mm/transparent_hugepage/enabled echo never /sys/kernel/mm/transparent_hugepage/defrag然后在 GRUB 的内核启动参数里追加transparent_hugepagenever修改 grub 配置后需要重启系统才能生效如果你在云环境不方便重启至少保留 sysfs 的运行时修改确保当前会话生效。CPU 调频模式同样值得关注。现在很多服务器 CPU 都支持动态调频默认的 powersave 或 ondemand 模式会根据负载动态调整 CPU 主频。对 TiDB 这种负载波动大的系统来说CPU 主频变化带来的性能差异可能达到 20% 以上而且调频过程本身有延迟高负载突然到来时 CPU 不能立即升到最高频率会造成响应延迟。建议把 TiKV 和 TiDB Server 节点 CPU 调频模式固定到 performance。cpupower frequency-set -g performance如果你的系统没有 cpupower 工具也可以在 BIOS 层面或虚拟化平台的安全组配置里把 CPU 性能模式固定。物理机优先在 BIOS 设置云主机则确认下虚拟化平台是否允许设置性能模式。2.3 文件描述符和进程数限制不设够直接翻车TiDB 集群跑起来之后文件描述符消耗远比想象中快。每个 gRPC 连接至少占用一个 fdTiKV 打开的数据文件可能上百个加上 WAL、临时排序文件、日志文件一台 TiKV 节点轻松突破几万个 fd。系统默认的 ulimit -n 通常是 1024 或 65535这个量级在数据库集群面前完全不够。我建议的配置是把 soft 和 hard 限制都调到很大同时把内核级的 file-max 也调上去。注意/etc/security/limits.conf 的配置只对通过 PAM 登录的会话生效如果你的 TiDB 进程是用 systemd 管理的还需要在 service 文件里显式声明 LimitNOFILE。cat /etc/security/limits.conf EOF * soft nofile 1048576 * hard nofile 1048576 * soft nproc 1048576 * hard nproc 1048576 EOF同时修改 sysctlfs.file-max 2097152 fs.nr_open 2097152这里有个容易忽略的坑nproc 的限制在某些容器环境或 systemd 环境下不会走 limits.conf而是受 cgroup pids 限制影响。如果部署在容器里要检查容器的 pids.max 是否足够。不然你会看到明明 limits.conf 已经改到了很大进程一启动还是报 Resource temporarily unavailable。vm.max_map_count 这个参数也很多人漏掉。默认值是 65530对 TiDB 这种大量使用 mmap 的应用来说很容易触发 “max_map_count” 超限的错误。建议调高到 262144 甚至 1048576这是一个怎么调大都不会有副作用的内核参数。3. 磁盘与文件系统TiKV 写入路径的隐形瓶颈3.1 挂载参数优化noatime 和 nodiratime 是基础文件系统挂载参数听起来很底层但对 TiKV 这种大量小文件读写的应用影响明显。Linux 默认挂载通常会记录文件访问时间atime每次读取文件都要更新这个时间戳产生一次额外的写操作。对数据库数据文件这种读多写少的场景这个行为纯粹是浪费 IO。建议在 /etc/fstab 中对数据盘挂载参数追加 noatime,nodiratime。如果数据盘已经挂载可以重新挂载生效mount -o remount,noatime,nodiratime /data这块还要考虑文件系统本身的选择。TiDB 官方对 TiKV 数据盘的推荐是 ext4 或者 xfs这两者在数据库场景下都有成熟的应用案例。ext4 胜在稳定和兼容性xfs 胜在大文件和高并发下表现更稳。从我自己的实际测试来看同样是 NVMe 盘xfs 在 64 线程并发小文件写入时抖动更小但 ext4 在单线程顺序读的性能峰值更高。如果你拿不准就用 ext4它是最稳妥的选择。3.2 I/O 调度器NVMe 时代选 none 几乎没悬念I/O 调度器负责把块设备的读写请求排序和合并。传统机械硬盘时代我们习惯用 deadline 或 cfq因为磁头寻道需要优化请求顺序。但到了 NVMe SSD 时代IOPS 足够高随机读写能力已经接近内存调度器反而会带来额外的 CPU 开销和排队延迟。对 TiKV 这种延迟敏感的数据库NVMe 盘使用 none 调度器基本是共识。查看和修改调度器cat /sys/block/nvme0n1/queue/scheduler echo none /sys/block/nvme0n1/queue/scheduler但这个修改重启后会失效要持久化的话需要写一个 systemd 服务或者在启动脚本中执行。网上有现成的 udev 规则写法可以绑定设备名执行cat /etc/udev/rules.d/60-io-scheduler.rules EOF ACTIONadd|change, KERNELnvme*, ATTR{queue/scheduler}none EOF如果用的是普通 SATA SSDdeadline 也可以但实测下来 none 在大多数场景下依然更稳。这里我建议做个微基准测试用 fio 分别压测两种调度器下的 4K 随机写延迟用数据说话比听别人推荐靠谱得多。3.3 脏页回写参数别让内核憋大招vm.dirty_ratio 和 vm.dirty_background_ratio 控制脏页占内存的比例。默认值分别是 20 和 10也就是说脏页占到内存 20% 时进程写操作会被阻塞强制回写占到 10% 时后台内核线程开始回写。这个默认值对数据库有个负面效应文件系统会攒着大量脏页一次性刷盘造成 IO 尖峰写入延迟出现周期性恶化。TiKV 的写入是持续性负载我们希望脏页尽快回写而不是积攒到阈值后集中爆发。所以建议把 dirty_background_ratio 调低dirty_ratio 也适度调低让内核更积极地把脏页刷下去。vm.dirty_background_ratio 5 vm.dirty_ratio 10不过这里有个细节当系统总内存很大比如 512G时比值类型的参数对应的绝对内存量还是很大。现代内核支持以字节为单位的 vm.dirty_background_bytes 和 vm.dirty_bytes这两个参数是互斥的设置了 bytes 就不能设置 ratio。大数据量机器上建议使用 bytes 精确控制例如设置 background_bytes 为 8Gdirty_bytes 为 16G具体值需要结合数据盘性能和业务写入速度来调整。调整后建议观察一段时间如果 TiKV 的 write stall 现象减少、写入延迟的 p99 更稳定说明方向是对的。4. 网络协议栈分布式通信的生命线4.1 TCP 连接队列和端口范围决定集群连接的承载上限TiDB 集群内每对节点之间都会建立多个 gRPC 连接PD 与 TiKV、TiDB 与 TiKV、TiDB 与 PD 之间都有长期连接。同时还要处理来自应用端的短连接和连接池里的连接。默认的 TCP 连接队列长度和端口范围都很保守连接数上来之后会出现排队和端口被占满的问题。关键参数是 net.core.somaxconn 和 net.ipv4.tcp_max_syn_backlog。somaxconn 默认是 128这是 accept 队列的最大长度连接建立速度快但应用处理不过来时新建连接会在这里堆积。TiDB 的 connection 处理协程通常很高效但突发流量下 128 明显不够看。建议net.core.somaxconn 4096 net.ipv4.tcp_max_syn_backlog 8192端口范围参数 net.ipv4.ip_local_port_range 默认在 32768-60999当节点间通信频繁建立新连接时这个范围的端口数可能不够用。客户端程序会在这个范围内取临时端口端口耗尽会表现为 connect timeout 或者 connect: cannot assign requested address。建议扩大到 1024-65535net.ipv4.ip_local_port_range 1024 65535这里要提醒一句端口范围调大后会占用更多内核内存用于 socket 管理但现代服务器上这点内存消耗可以忽略不计。4.2 网络缓冲区告别的“太小”问题Linux 网络协议栈默认的读写缓冲区相对保守。net.core.rmem_max 默认 212992约 208KBwmem_max 同样。对于 TiKV 之间需要传输的大批量数据块比如一次批量扫表返回的 KV 数据这个缓冲区大小会导致吞吐量受限和延迟增加。建议把最大缓冲区调大到 16MB 或 32MBnet.core.rmem_max 16777216 net.core.wmem_max 16777216 net.core.rmem_default 262144 net.core.wmem_default 262144注意调整的是 max 而不是 default因为 TCP 连接会根据实际负载动态调节缓冲区到合理范围但上限如果卡死性能就上不去。之前踩过坑只调了 default 没调 max结果吞吐量上去了缓冲区却上不去白白踩了几个月。TCP 拥塞控制算法也讲一讲。默认的 cubic 算法是通用场景设计延迟敏感的内部网络环境可以尝试 bbr。BBR 在高带宽、高延迟链路上效果明显内网低延迟场景下提升没那么夸张但也不会变差。net.core.default_qdisc fq net.ipv4.tcp_congestion_control bbr内核支持 bbr 需要 4.9 以上版本云虚拟机 4.14 或更高内核基本都没问题。验证方式sysctl net.ipv4.tcp_congestion_control输出的应该是 bbr。4.3 网卡队列和中断绑定多核利用率的关键现代服务器网卡普遍支持多队列 RSS默认情况下网卡中断会分配到一个 CPU 核上这在网络流量大时会形成单核瓶颈。TiDB 节点间的 gRPC 流量可能达到几 Gbps单核处理中断很容易把 CPU 打满。先看网卡支持多少队列ethtool -l eth0如果 Combined 队列数大于等于 CPU 核数可以直接设置多队列并用 irqbalance 服务做动态负载均衡。但 irqbalance 有时会把中断调来调去数据库场景下更推荐手工给中断绑定固定的 CPU 核心减少切换开销。ethtool -L eth0 combined 8中断号可以查看 /proc/interrupts 找到网卡对应的那一行然后用 echo 把 CPU 亲和性写入 /proc/irq/N/smp_affinity。这类配置是硬件相关的每台机器可能不同我一般是写一个初始化脚本在系统启动时自动执行。还有一个参数容易忽略就是网卡队列的长度 net.core.netdev_max_backlog。默认值 1000 在突增流量下会丢包。建议net.core.netdev_max_backlog 163845. 可落地的配置模板与验证方法5.1 sysctl.conf 完整模板下面给一个标准化配置模板适合 TiKV 节点使用。TiDB Server 节点可以去掉磁盘相关的部分其他保持一致。cat /etc/sysctl.d/99-tidb.conf EOF # 内存管理 vm.swappiness 1 vm.max_map_count 1048576 vm.dirty_background_ratio 5 vm.dirty_ratio 10 # 文件与进程 fs.file-max 2097152 fs.nr_open 2097152 # 网络 net.core.somaxconn 4096 net.ipv4.tcp_max_syn_backlog 8192 net.ipv4.ip_local_port_range 1024 65535 net.core.rmem_max 16777216 net.core.wmem_max 16777216 net.core.rmem_default 262144 net.core.wmem_default 262144 net.core.netdev_max_backlog 16384 net.ipv4.tcp_fin_timeout 15 net.ipv4.tcp_tw_reuse 1 net.ipv4.tcp_slow_start_after_idle 0 EOF sysctl --systemtcp_tw_reuse 这里补充说明它允许内核在安全前提下复用 TIME_WAIT 状态的连接对减少短连接场景下的端口占用和连接建立时间有帮助。tcp_slow_start_after_idle 设为 0 是为了让空闲后的连接不降低初始拥塞窗口对长连接的周期请求有帮助。5.2 参数生效与持久化验证别改完就跑修改完参数后一定逐项验证不要以为 sysctl --system 执行成功就万事大吉。我见过太多次配置写进去了但实际值没变的情况。一个经典案例是 /etc/sysctl.d/ 目录下多个配置文件冲突后加载的配置覆盖了先加载的或者你有同名 key 写在 /etc/sysctl.conf 里优先级不同。正确的验证姿势是逐个确认参数期望值验证命令vm.swappiness1sysctl vm.swappinessvm.max_map_count1048576sysctl vm.max_map_countfs.file-max2097152sysctl fs.file-maxnet.core.somaxconn4096sysctl net.core.somaxconnnet.ipv4.tcp_congestion_controlbbrsysctl net.ipv4.tcp_congestion_controlTHP enablednevercat /sys/kernel/mm/transparent_hugepage/enabled对于文件描述符限制要确认真正运行 TiKV 的用户是否生效su - tidb -c ulimit -n如果显示的不是预期值检查 /etc/security/limits.conf 是否写对了用户段以及是否有 systemd service 覆盖了 LimitNOFILE。5.3 用监控数据验证优化收益不要凭感觉调优后如何确认有效最直观的手段是用压测工具做前后对比。TiDB 社区常用的压测方式有两种一种是用 SynchroBench 跑 tpcc 或 tpc-h 类负载另一种是直接用 sysbench 对单表做点查和写入。我更推荐先做资源层验证再跑业务层负载。资源层的验证命令如下# 查看 swap 使用情况 vmstat 1 # 查看内存回收产生的 CPU 消耗 cat /proc/vmstat | grep -E pgscan|pgsteal # 查看网络重传率 netstat -s | grep retrans观察 vmstat 的 si 和 so 列正常情况下优化后这两列应该保持为零或者非常低。网络重传如果是 0.01% 以内属于正常如果超过 0.1%就要考虑网卡队列、缓冲区配置是否还有问题。还有一个更直接的指标是 TiDB 监控面板里的 TiKV gRPC 延迟和 Raft propose 延迟。优化前如果 Raft propose 的 p99 曲线有明显周期性尖峰优化后尖峰应该变得平缓这正是脏页回写和 I/O 调度器优化的功劳。6. 常见问题排查与经验沉淀6.1 配置不生效的排查思路遇到参数配置不生效先按下面的路径排查。首先确认优先级问题/etc/sysctl.d/ 下面的文件按文件名顺序加载99-tidb.conf 的优先级最高如果有其他文件在它之后覆盖了同一参数就会被冲掉。用 sysctl --system 可以看到所有生效文件及其顺序。其次要注意有些参数是只读的例如 fs.nr_open 在某些内核版本下不能动态修改需要重启。net.ipv4.tcp_congestion_control 也有个坑如果内核模块没加载 tcp_bbr写入 bbr 会报错需要先 modprobe tcp_bbr。最容易被忽略的是系统安全软件或虚拟化平台自带的优化配置。比如某些云厂商的镜像里已经内置了一个 tuned 服务tuned 会周期性覆盖 sysctl 参数你手动改完tuned 一刷新又变回原值。解决办法是把 TiDB 节点加入 tuned 的例外列表或者直接停用 tuned 服务。6.2 优化过度引发的连锁反应参数不是越大越好这是我在实际运维中反复验证过的教训。有次我把 vm.dirty_bg_ratio 调得过低1%后台刷盘线程几乎不停工导致 CPU 的 iowait 暴涨TiKV 的 RocksDB compaction 频繁被刷盘抢占 IO。后来调回到 5% 配合 dirty_bg_bytes 精确控制才稳定下来。还有一个典型问题是 TCP 缓冲区开得过大。如果 rmem_max 设置到 32MB 而机器内存只有 32G当有几百个连接同时活跃时单连接占用的内核内存可能达到十几 MB整体内存消耗立刻上去。缓冲区大小要结合带宽和延迟计算公式是 BDP 带宽 × 延迟例如万兆网卡内网延迟 0.2msBDP 大约是 2MB设置 16MB 已经留了很大余量不必继续翻倍。6.3 我的避坑清单最后整理一份我自己踩过坑后沉淀下来的清单给要上生产环境的同学参考。第一数据盘和系统盘分离操作系统参数中涉及挂载选项和 I/O 调度器的配置只针对数据盘不要对系统盘做同样的特殊处理系统盘的默认配置更稳妥。系统盘跑日志、监控、工具链其 IO 特征和数据盘完全不同。第二修改内核启动参数前务必备份 grub 配置。之前某次在云主机上给 grub 追加了 transparent_hugepagenever结果云厂商的启动脚本不兼容重启后起不来了。后来统一改成在系统服务启动阶段写入 sysfs 的方式虽然晚了几秒钟生效但至少不会起不来机器。第三不要在一台机器上混跑 TiDB 节点和负载生成工具。压测机如果和生产节点共用同一台物理机IO 和网络互相干扰优化验证的结果根本没有参考价值。第四每次调整完参数把前后两天的监控截图存档标注调整了哪些参数方便出问题时回滚对比。这能省去很多“玄学”排查时间。分布式系统的问题往往是多个因素叠加记录能帮你定位到具体是哪个参数起了正面或负面作用。操作系统参数优化这块急不得也懒不得。每个参数背后都有明确的机制和适用场景建议在你自己的测试环境里先压一轮把不适合理论共识的参数找出来再上生产。毕竟每个团队的磁盘类型、网络环境、负载模型都不一样只有亲手验证过的配置你才敢在半夜被叫起来处理故障的时候心里有底地说一句“这个值就是对的”。