资讯动态

MySQL单表数据量多大该分库分表?性能拐点与优化策略

发布时间:2026/10/21 15:38:22 来源:尧图企业网站定制
1. 先搞清楚问题真正问的是“性能拐点”不是“磁盘装不装得下”很多团队在讨论要不要分库分表时第一句话就是“单表超过多少行会卡”。我以前带过的几个项目里几乎每个月都有人拿这个来问。这个问题看起来很简单但十次里有八次都问错了方向。MySQL单表到底能存多大如果只讲理论容量InnoDB的单表空间上限可以到64TB行数上亿没有任何物理障碍。但你真把一张表怼到1亿行应用层大概率不会好受。所以大家真正关心的是另一个问题单表存到多少行性能会开始出现肉眼可见的劣化这个“性能拐点”才是值得认真讨论的东西。1.1 “几千万行”的传言到底怎么来的行业内流传最广的说法是“单表5000万行就要分表”。这个数字不是完全没有道理它的背后是InnoDB索引结构B树的高度问题。InnoDB默认页大小是16KB主键如果是bigint8字节每个非叶子节点大概能存放一千多个指针。三层B树能覆盖的叶子页数量大约是1170×1170≈136万个假设每个叶子页平均能装下20~50行数据那么三层结构能支撑的行数大约在2700万到6800万之间。到了这个量级树的高度从3层往4层跨越随机点查会多一次磁盘I/O。从实测看这个变化不是“从快变慢”的天壤之别而是从亚毫秒级变成1到2毫秒级的差别。OLTP高并发场景下单次查询多1毫秒叠加到几千并发上整体吞吐就能明显感受到下降。所以“5000万”这个数字本质上是B树层级演变的一个经验临界点它只反映了点查延迟成本并不能代表所有查询模式。很多文章把这个数字当成铁律导致一帮人还没到千万行就开始焦虑另一帮人已经两个亿了还在硬扛。实际上数据量只是影响性能的一个变量访问模式才是决定单表容量上限的核心变量。1.2 磁盘容量从来不是单表行数的决策依据先做个简单换算。单行数据平均1KB的话5000万行就是50GB左右这个容量对现代服务器来说完全不是问题。单行2KB1亿行就是200GBSSD照样放得下。再说句实话磁盘是最不值钱的资源一张表占200GB还是占20GB绝大多数团队根本不需要为此做技术决策。这引出一个常见的认知误区拿“表总大小”或者“总行数”当阈值却忽略了热数据的访问范围。MySQL的InnoDB有buffer pool默认会尽量把热数据页放在内存里。如果你们的活跃数据只有几个GB哪怕整张表有2亿行大部分点查都命中了内存磁盘I/O压力没有想象中大。反过来如果一张表只有5000万行但查询每次都在扫描上亿行级别的冷数据数据库照样被拖垮。所以我的建议是讨论单表容量之前先讨论业务访问形态。这是所有后续判断的地基。2. 决定单表容量的关键变量访问模式远比行数重要数据库的负载归根结底是磁盘I/O和内存命中率的博弈。访问模式决定了你要花多少次I/O才能拿到结果行数只是放大这个成本。2.1 点查、范围查询、统计扫表三类访问形态点查等值查询走主键或唯一索引目标是快速捞回一行或几行。这种访问对单表行数最不敏感。我见过单表超过1.8亿行的用户维度表所有查询都带主键或用户ID索引日常跑得相当稳。点查的成本主要看B树的高度前面算过只要树的高度不变行数翻一倍对延迟影响很小。范围查询比如按时间范围、用户ID区间去查一页数据走二级索引回表。这种访问的瓶颈在于“回表行数”。如果二级索引过滤后还要回表取一万行单表行数越大回表的随机I/O就越密集延迟自然上去。统计扫表全表聚合、报表统计、不带索引的过滤条件。这种访问跟行数几乎成线性关系500万行和5000万行的扫描时间差一个数量级。这类查询一旦出现在高频路径上不管单表多少行都会出事唯一的出路是换引擎、上汇总表、或者做列存储。2.2 千万行单表为什么在有些业务里毫无压力我之前处理过一个订单场景的库单表稳定在五六千万行左右。主表字段不算多二三十个列平均行宽不到300字节。查询特征非常清晰用户查自己的订单条件永远是user_id加create_time范围分页取20条。这个表的索引设计是(user_id, create_time)联合索引。因为二级索引过滤后只需要回表取20行每次查询实际读取的量非常小。加上这五六千万行里最近半年的热数据占比不到两成buffer pool完全盖得住。所以几年下来这套结构没有出现明显的性能劣化。这个案例想说明的道理很简单查询访问的数据量决定了数据库要付出多大的成本而不是全表有多少行。2.3 500行的小表也能把数据库拖垮的几个反例别以为“单表小”就万事大吉。有一个坑我值得单独说一下表很小但行锁竞争极热。有个配置类的表全表不到2000行但业务方把“防重校验”做成了每次操作都先SELECT再UPDATE范围覆盖同一个sku的库存行。每到高峰这条UPDATE排队严重几十毫秒的请求能拖到两三秒数据库的线程池直接被占满连带其他表的查询一起变慢。这就是经典的“热点行更新”问题——数据量小不等于并发压力小。定位问题的时候不能把目光只锁定在表行数上慢查询日志、锁等待事件、thread running这些指标往往比“行数多少”更能说明问题。还有一个反例是查询计划出问题。小表上如果索引写得不合适优化器可能走全表扫或者因为字段类型不一致导致索引失效比如字符串和数字隐式转换一千万行的小表也能扫出一身冷汗。3. 被绝大多数人忽视的成本二级索引、写放大与页分裂讨论单表容量时很多人只盯着主表那几千万行的存储完全忘了二级索引带来的连锁反应。这一块才是真正吃性能、吃运维资源的地方。3.1 索引不是免费的是写路径上的隐性税InnoDB的二级索引也是B树而且数据量越大索引树的层级和体量就越可观。一张1000万行的表每增加一个二级索引就相当于额外增加了一棵百万级节点的树。写入的时候主表要更新一次每个二级索引也要跟着更新一次。换句话说你有三个二级索引的时候一次INSERT实际承担的是四次随机写入I/O。这在写入TPS较高的情况下代价非常明显。之前帮一个项目做过排查单表才800万行写入性能却一直上不去。后来数了一下表上挂了11个索引其中四五个是之后业务迭代加的但一开始的查询需求早就没了一直没人清理。把没用的索引删掉后写入延迟直接降了四成。这个例子我一直放在复盘文档里当作反面教材。所以评估一张表能不能扛住更多数据先数一遍索引再去谈行数阈值。不用的索引要敢删别被“万一哪天用得上”的心态绑住。3.2 UUID主键与页分裂行数没涨多少表空间却涨了主键设计对单表容量的影响比很多人想象中大得多。自增主键插入的时候是顺序写MySQL只要往当前页尾部追加就完成了效率极高。而UUID主键是随机分布每次插入都可能落在任意位置导致页分裂频繁发生。页分裂的结果是表空间出现大量碎片明明只有5000万行实际占用的磁盘和内存可能远超预期。更麻烦的是碎片会降低buffer pool的有效利用率同样一个16KB页如果只装了一半数据能缓存的行数就变少了。有一次排查单表容量问题时发现一张行数只有3000万的表ibd文件却到了45GB而理论上应该只有20GB出头。查下来就是主键用了字符串UUID带了7个二级索引碎片率极高。解决方案有两种一是把主键改成bigint自增配合UUID列做普通索引保证全局唯一二是如果实在改不了主键就定期做一次表重建来消除碎片比如用pt-online-schema-change或者直接ALTER TABLE 表名 ENGINEInnoDB低频执行彻底重排数据页。注意这个操作在超大表上要谨慎提前评估窗口期和磁盘空间。3.3 大字段行外存储的连锁反应TEXT、BLOB这类大字段当单行内容超过一定阈值时InnoDB不会把它们全塞在聚簇索引的叶子页里而是只存一个前768字节的“前缀”剩下的内容放到溢出页里。这带来一个隐患查询大字段的时候即使只是SELECT某一行的TEXT列也可能需要额外的I/O去读溢出页该行的性能直接和“这个字段到底有多大”挂钩而不是和表的总行数挂钩。业务上稍不注意一张普通的用户表里塞了个“用户协议全文”“操作日志长文本”之类的列单行平均宽度就会被拉上去。行宽一大同样一个16KB页能放的记录数就少了B树的叶子层页数变多树的层级和占用的buffer pool空间也跟着涨。我的建议是大字段单独拆表存主表只留ID和引用关系。查询用户列表时就不要去碰大字段所在的表需要详情时再按需读取。这个设计看起来多了一次关联实际对OLTP场景的整体容量和延迟收益非常明显。4. 运维视角的硬约束备份、恢复与DDL耗时数据量对在线查询的影响可能会被高估但对运维操作的影响一定是实打实的。这一章聊的是一些决策者容易忽略的非功能约束。4.1 一张表的大小如何决定故障恢复时长先说备份。一张核心业务表500GB用物理备份工具做全备可能还好一两小时能完成如果只能用逻辑备份mysqldump导出来动辄三五个小时这还只是备份还没算恢复时间。最怕的是半夜出故障需要临时恢复数据。一张400GB的InnoDB表从备份中恢复并重放binlog到指定时间点整个过程通常以“小时”为单位起步。生产环境的恢复窗口不是无限长的如果SLA要求小时级恢复那单表容量就必须主动控制在某个量级以内这个量级取决于你们团队的基础设施和备份策略而不是纯粹的技术极限。我的经验是单表体积如果超过300GB就应该启动拆分流程了。这个“300GB”不是一个精确的科学数值而是当表达到这个量级时很多常规运维动作都开始变得笨重备份耗时变长、恢复窗口拉大、磁盘占用增速过快。4.2 Online DDL也不是免费的MySQL从5.6开始支持Online DDL但这话不要理解成“线上随便加字段”。大表加字段确实可以做到不停机但它背后的机制没那么善良。ALTER TABLE增加列或修改字段类型时InnoDB底层往往需要重建整张表。这个过程有两个重要副作用第一需要有足够的临时空间重建期间磁盘使用峰值可能达到原表体积的1.5倍以上。有一次在磁盘非常紧张的情况下执行一个在线加索引操作直接就把磁盘打满了导致实例挂掉。第二重建表期间虽然有online能力的优化但要复制数据页、更新索引实际上还是在消耗大量的I/O和CPU。几百GB的大表上执行DDL拖慢全实例其他查询是家常便饭。所以在评估单表容量的时候要把“未来一段时间要不要加字段、加索引”纳入考量。表越大DDL的风险和成本越高这直接决定了你的技术团队有没有底气在未来几个月里快速迭代。5. 拿来就能用的决策表单表容量自查清单说了这么多给一个可以实际落地的判断框架。别去记“5000万”这种死数字按下面的方法评估更符合业务实际。5.1 先回答五个问题问题判断指向当前单表有多少行、占多大空间先跑一次统计用数据说话每月增量多少一年后预计多大预测未来容量别只看当下主要查询是主键/唯一索引点查还是范围查询点查占比高则单表上限更高有没有周期性全表扫描或复杂聚合有的话要单独解决不只看行数写并发高吗上线后还要不要频繁DDL决定运维成本和拆分时机用SQL先给表做个“体检”SELECT table_name, table_rows, ROUND((data_length index_length) / 1024 / 1024, 2) AS size_mb FROM information_schema.tables WHERE table_schema your_db ORDER BY size_mb DESC;table_rows是估算值InnoDB不保证准确但它能给你一个量级上的参考。真实的精确行数可以用COUNT(*)确认一次代价比较大非必要不死磕。还要用EXPLAIN看几个核心查询的执行计划重点检查有没有typeALL或者rows预估过大的情况EXPLAIN SELECT * FROM orders WHERE user_id 123456 AND create_time 2024-01-01 ORDER BY create_time DESC LIMIT 20;如果rows预估在几万以下这个查询基本没有压力如果rows预估到百万级以上不管全表多少行这个查询都是需要关注的隐患。5.2 三类典型场景的实践参考值业务形态常见适合量级关键原因交易订单类百万到千万级常用多维查询高频写入索引较多用户主数据类千万到亿级点查为主数据稳定索引少日志流水类单分区百万到千万级持续写入冷热分离定期清理交易订单表是分库分表的重灾区。订单的查询条件非常多样按用户查、按商家查、按时间查、按状态查每一种查询都对应可能的索引索引多了写入就会变重。我的习惯是订单表尽量控制在千万级别再往上就要认真考虑按月分表、按租户分库或者归档历史订单。用户主数据表相对佛系。用户表、商品表这类以ID点查为主的数据单表撑到一两亿都不稀奇重点要控制好二级索引的数量和热数据的缓存命中率。日志流水表是反面典型。日志表天生只增不减如果没有分区和清理策略涨到几千万行只是时间问题。这类型表我建议直接建分区按天或按月分区保留最近N个月的热数据超期的分区直接DROP。分区不代表你能随便查但至少给了你快速清理数据的后路。5.3 超过阈值后的优先动作顺序不要一超标就冲上去做分库分表。分布式改造的成本非常高而且它解决的是容量和写入吞吐问题不是查询慢的万能药。性价比从高到低通常是这么个顺序第一步先做索引和查询优化。删掉无效索引、补上缺失的联合索引、改写慢查询、让统计报表挪到只读备库或数仓——把能白嫖的性能先白嫖回来。第二步做冷热数据分离。核心业务表里80%的旧数据可能没什么访问热度了先归档到独立的表或者OSS里的冷存储主表瘦身后自然就轻松了。注意别用DELETE硬扛DELETE本身会产生大量binlog和碎片官方推荐的方式是用分区表按时间滑动或者用工具分批删。第三步再考虑分区表。分区在MySQL里的适用范围比较窄分区键必须是主键或唯一索引的一部分。它的意义主要是方便管理像按时间滚动清理数据、减少索引过大带来的维护成本。分区不会自动让查询变快如果你把分区键写不进查询条件数据库反而要扫全部相关分区比全表扫还慢。最后才是分库分表。只有单表到了千万行以上且存在单key写入热点、或者单表体积大到运维工具操作困难、或者未来增量在一年内会翻番才值得启动水平拆分。6. 我的一点实战体会做了这么多年数据库优化我越来越觉得单表容量这类问题不应该靠一个“江湖传言”式的数字来拍板。数据量本身不是敌人没有规划的访问模式、失控的索引数量、过于随意的表结构设计这些才是把单表推向深渊的幕后推手。我自己在项目里的做法是给每张核心表建立一套“增长档案”每季度记录一次行数、表大小、核心查询耗时、慢查询数量等指标。这些指标形成趋势以后比任何“5000万行”的说法都更有指导意义。感觉曲线开始在某个位置陡峭起来那才是真正需要动手的时刻。另外还有两个小建议供参考第一主键尽量用bigint自增除非有分布式全局ID的强需求否则不要为了“统一规范”给自己挖坑第二任何核心新表上线提前确认好它的未来量级上限和清理计划有些表从出生那一刻就不该设计成一张无止境增长的单表。单表存多少合适抛开业务谈数字都是耍流氓。把本文的几类场景套到自己的项目里过一遍你得到的答案会比任何“标准答案”都靠谱。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑