MySQL千万级大表优化实战:慢查询、索引与SQL改写全指南

发布时间:2026/10/7 2:56:24
MySQL千万级大表优化实战:慢查询、索引与SQL改写全指南 两年前我接手过一个电商订单系统订单表在业务增长期几乎每天都新增三四十万行半年不到就冲上了千万级。某天凌晨收到告警一条用来做后台统计的 SQL 跑了接近 40 秒直接把一个核心查询接口拖到超时。从那天开始我算是被 MySQL 大表优化正式“教育”了一遍所谓千万级数据的性能瓶颈极少是单个问题而是一串被忽略的细节——索引没覆盖、SQL 写得糙、表结构埋雷、数据越堆越多。这篇文章不打算讲理论大纲而是围绕我实际踩过的坑把排查路径、索引设计、SQL 改写、表结构取舍和架构升级这几块挨个说透。无论你的表现在只有几百万行还是已经涨到几千万行这套打法都能直接落地。1. 千万级表的“卡顿”要从哪里开始排查1.1 慢日志和 EXPLAIN 是定位慢 SQL 的第一站很多人一听到“大表慢”就想着加配置、换机器实际上先搞清楚慢在哪一步才是关键。我接手项目第一天第一件事就是把慢查询日志打开。MySQL 5.7 里可以直接动态开启set global slow_query_log ON; set global long_query_time 1; set global log_queries_not_using_indexes ON;long_query_time 设成 1 秒意思是超过 1 秒的 SQL 才会落日志。然后让系统跑上一两个小时看看哪些 SQL 频繁出现在日志里。这样做往往比你自己猜“哪几条 SQL 比较慢”要可靠得多因为真实业务里占大头的慢查询经常是你完全没注意到的边缘接口。拿到慢 SQL 之后下一步不是急着改而是用 EXPLAIN 看执行计划。重点看三样东西type 列、rows 列和 Extra 列。type 列如果出现 ALL说明这条 SQL 在做全表扫描。千万级表里全表扫描意味着几千万行数据要从磁盘刷到内存几乎等于自杀式查询。rows 列是优化器预估扫描的行数它直接告诉你这条查询的“胃口”有多大。rows 一旦到 100 万以上哪怕 SQL 本身不长也基本可以断定索引没设计好。Extra 列如果出现 Using filesort、Using temporary说明查询里隐含排序或者临时表操作。这类操作在千万级表上的代价往往比索引扫描还要大得多。我习惯把慢日志里的 TOP SQL 再用 pt-query-digest 汇总一遍看看哪些 SQL 的总执行时间占比最高。注意不一定执行次数最多的就是最需要优化的有时候一条每天只跑几次、但一次就 30 秒的统计 SQL对系统的伤害远大于一万条 200 毫秒的查询。1.2 先分清慢的根源磁盘 IO、锁等待还是 SQL 本身拿到慢 SQL 之后还得判断瓶颈到底落在哪一层。同一个“大表慢”背后的原因可能完全不同乱动结构反而浪费时间。我常用的方法是先看两个指标。第一个是 InnoDB Buffer Pool 的命中率。Buffer Pool 是 MySQL 在内存里缓存表数据和索引的地方命中率越高说明查询越靠内存命中率低了说明大量查询在真实读磁盘问题往往出在索引设计不够好或者内存分配不足。SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_read_requests; SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_reads;命中率 read_requests / (read_requests reads)。千万级表上这个值如果低于 95%甚至掉到 90% 以下就要非常小心了。内存面是有上限的如果一条 SQL 需要扫描 500 万行而 Buffer Pool 里只有最近热门的 2000 万行索引页那它必然会把大量旧页挤出缓存进而引发连锁的磁盘 IO。这也是为什么“加内存”有时候有效但治标不治本——真正的解法通常是让 SQL 扫更少的行。第二个是锁等待。大表上最常见的一个坑就是长事务。某个连接开启事务后执行了很长时间的 UPDATE却一直没提交它持有的行锁会挡住所有其他操作。这时候从慢日志里看到的现象是一条看似很简单的 INSERT 都执行了几秒钟但它本身并不慢而是被前面的长事务卡住了。排查锁等待可以直接查两个视图SELECT * FROM sys.innodb_lock_waits\G SELECT * FROM information_schema.innodb_trx\G我把常见瓶颈的特征整理成一个简单的判断表排查的时候按表对照比较快瓶颈类型典型现象优先检查项磁盘 IO 占比高慢 SQL 集中在大量扫描Buffer Pool 命中率偏低EXPLAIN rows、Buffer Pool 命中率、索引覆盖情况锁等待简单的 INSERT/UPDATE 也慢阻塞时间不固定sys.innodb_lock_waits、innodb_trx 长事务SQL 自身低效只有特定几条 SQL 慢系统整体状态稳定慢日志、执行计划、索引是否失效1.3 建立容量基线量化你的表到底“重”不“重”我见过不少团队一说大表优化就凭感觉说“这表有几千万行”但具体多大、索引多大、平均一行多少字节一概不知。没有基线后面所有优化都是猜。要量化一条 SQL 就够了SELECT table_name, table_rows, data_length, index_length, ROUND((data_length index_length) / 1024 / 1024, 2) AS total_mb FROM information_schema.tables WHERE table_schema your_db AND table_name order_info;以我当时那个订单表为例2600 万行data_length 大概 28GBindex_length 大概 12GB。除以行数算出来平均一行数据大约 1.1KB加上索引整体接近 40GB。这个数字什么概念呢InnoDB 默认页大小 16KB一页大概能装 14 行如果做一次全表扫描就是大约 180 万个页要读而 Buffer Pool 当时只有 8GB连这份表的索引都塞不下。所以那个跑了 40 秒的统计 SQL本质上不是 SQL 语法问题是它选择了全表扫而全表扫在这个量级下根本没有活路。基线还能帮你想清楚另一个问题怎么让全表扫描尽量不出现只要业务查询不总是要求返回整张表就一定有办法用更小的数据集合代替全表扫。2. 索引设计先想清楚查询路径再建索引2.1 覆盖索引和回表的血泪账大表优化的第一战场永远在索引。建索引不是“哪个字段查得多就给哪个字段加”而是先看清执行计划里有没有欠下“回表”这笔账。所谓回表就是 InnoDB 在主键索引上存了完整数据行而二级索引只存了索引字段和主键值。你查询的字段如果不在二级索引里InnoDB 就得拿着主键值去主键索引里再找一次完整行。听起来没什么但在千万级表上一次回表就是一次随机读。如果某个用户的查询命中了 1000 行订单那就是 1000 次随机读如果这些行分散在几十个数据文件块里Buffer Pool 又没缓存延迟会成倍放大。我那个订单表早期有一个统计查询逻辑不复杂SELECT order_id, user_id, status, pay_time FROM order_info WHERE user_id 123456 AND status IN (1, 2, 3) ORDER BY pay_time DESC LIMIT 20;当时的索引只有 idx_user_id(user_id)EXPLAIN 出来 typeref看着挺正常但 Extra 里没有 Using index意味着每一条命中的订单都要回表。用户订单多的时候一次查询就是几百次随机 IO。后来我把索引改成联合索引ALTER TABLE order_info ADD INDEX idx_user_status_pay (user_id, status, pay_time);再跑 EXPLAINExtra 里出现了 Using index说明这个查询要的所有字段都能从二级索引上直接拿到一次回表都不需要。查询耗时从 300 毫秒级别降到了 20 毫秒级别。这就是覆盖索引的威力让查询所需字段“锁”在索引里不给随机读留机会。2.2 联合索引字段顺序怎么排才能不浪费联合索引的顺序不能拍脑袋决定。索引本质上是一棵多列排序的树排在后面的列只有在前面列相等时才能发挥作用。比如索引 (user_id, status, pay_time)它支持“user_id 等值 status 等值 pay_time 范围”这种条件完全走索引但如果查询只带 status 不带 user_id这个索引基本帮不上忙因为最左边的 user_id 没有被约束住。我的排法是遵循三条原则第一等值条件字段放最前面第二范围条件字段放在等值字段之后第三排序字段尽量放在索引的尾部这样 ORDER BY 可以直接顺着索引顺序读避免 filesort。字段内部也有优先级。同一个联合索引里如果两个字段都是等值查询条件把区分度高的放前面。区分度可以用这个公式粗略估计SELECT COUNT(DISTINCT user_id) / COUNT(*) FROM order_info;比如 user_id 的区分度接近 0.8status 只有 0.01那显然 user_id 应排在前面因为它能把扫描范围快速缩小。但如果业务查询永远带 user_idstatus 在不在前面对单次查询影响不大只是会影响索引的“复用价值”。2.3 索引不是免费午餐写入放大和冗余索引索引越多查询越爽这是新手阶段最容易掉的坑。我见过一张表被加上将近 20 个索引结果每条 INSERT 都要往 20 棵索引树里各插一条记录。千万级数据量的表写入路径本身就重再把索引堆上去高峰期插入直接卡到怀疑人生。InnoDB 的二级索引插入还可能触发页分裂。索引页塞满后新插入一条记录就要申请新页把旧页的数据拆过去。表越大、索引越碎页分裂越频繁。这就是为什么业务上“要给一个字段加索引”的时候我总会要求先把已有索引列出来检查一遍。经常出现的情况是之前为了 A 查询建了 (a, b)后来为了 B 查询又建了 (a, b, c) 或者 (a, c)实际上前者有很多场景是冗余的。判断冗余最直接的办法是查 sys.schema_unused_indexes把长时间没人用的索引记录下来再结合 EXPLAIN 的 key 列确认没有 SQL 误用然后才敢删。我的习惯是单表二级索引数量控制在 5~6 个以内宁缺毋滥。这个数字不是硬标准但它会逼着你认真思考每个索引的服务对象而不是每遇到一条慢 SQL 就加一个新索引。2.4 索引下推一个容易被忽略的免费“截流”机制MySQL 5.6 开始支持的索引下推Index Condition PushdownICP在大表查询里经常被低估。简单说在没有 ICP 之前InnoDB 根据索引只能定位到最左边的一批记录然后回表读取完整行再在 Server 层过滤后面的条件。有了 ICP引擎会在索引扫描阶段就把不满足条件的记录剔除掉真正需要回表的行数大幅减少。还是拿订单表举例索引是 (user_id, status, pay_time)查询条件是 user_id 123456 AND status IN (1,2,3) AND pay_time 2024-01-01。如果没有 ICP引擎会把 user_id123456 的所有订单都从二级索引找出来再回表逐条看 status 是否符合有 ICP 之后status 和 pay_time 的过滤直接发生在索引扫描阶段回表次数少了一个数量级。这个机制不需要你写特殊 SQL只要版本满足、优化器认为可行就会自动用。真正需要做的是把可以下推的过滤条件尽量写成普通等值或范围条件而不是用函数包裹字段因为函数会打破 ICP 能利用的范围。3. 慢 SQL 改写与深分页的重构实践3.1 深分页为什么越翻越慢offset 的代价后端接口分页可以说是大表性能最集中的“翻车现场”。前端要第 50 万页后端就写SELECT * FROM order_info WHERE status 1 ORDER BY id LIMIT 1000000, 20;这条 SQL 看起来没什么问题但道理很扎心InnoDB 要从满足条件的记录里从第 1 行开始数一直数到第 1000020 行才能取出最后 20 行。前面那 100 万行虽然不会返回给客户端但引擎仍然要扫描它们。等于你只是想看 20 条数据数据库却先翻了 100 万条“废行”。我优化这类分页最优先的方案是改掉接口的翻页协议用游标分页替代 offset 分页。把上一页最后一条记录的 id 作为下一页的起点SELECT * FROM order_info WHERE status 1 AND id 1280000 ORDER BY id LIMIT 20;这样引擎直接从 id1280000 往后扫 20 行复杂度从 O(offset) 降为 O(pageSize)。代价是跳页体验变差用户不能直接点最后一页但很多业务场景根本不需要无限制的深跳转配合“上一页/下一页”按钮完全够用。3.2 延迟关联把回表和工作量一起降下来如果产品就是要求支持任意页跳转offset 分页不能彻底消失那就要用延迟关联来改写。思路是先在一个覆盖索引上完成分页定位只取出少量主键再拿这些主键去关联原表取完整字段SELECT t.* FROM order_info t JOIN (SELECT id FROM order_info WHERE status 1 ORDER BY id LIMIT 1000000, 20) tmp ON t.id tmp.id;子查询的 SELECT id 和 WHERE status1 都能用二级索引 idx_status_id 直接覆盖不需要回表它在 100 万行的索引扫描后只产出 20 个 id。外层 JOIN 再根据这 20 个主键去原表取完整行回表次数只有 20 次。对比原来的写法原 SQL 是边扫边回表 100 万行延迟关联是“索引扫 100 万行 20 次回表”。同样是扫 100 万行但少了 100 万次随机读效果是天壤之别。这个模式非常好用尤其是在 MySQL 5.6 以上版本子查询还能配合 ICP进一步降低扫描成本。3.3 ORDER BY、GROUP BY 的排序陷阱filesort 和临时表千万级表上做排序和聚合是最容易让内存被打爆的场景。EXPLAIN 里 Extra 一旦出现 Using filesort就意味着 MySQL 要额外开辟一块内存或者超出内存后落到磁盘临时文件来排序。排序的数据量一大性能直接崩。让排序走索引是最干净的解法。比如 WHERE user_id? AND ORDER BY pay_time索引 (user_id, pay_time) 就能让排序直接顺着索引顺序输出Extra 里不会再出现 filesort。反过来如果索引是 (user_id, status)ORDER BY pay_time排序字段不在索引的有效序列里MySQL 只能自己排。所以前面说的“排序字段放索引尾部”不是洁癖而是实打实的性能策略。GROUP BY 的本质也是先排序再分组所以同样的思路适用。但坦白说千万级表上频繁做 GROUP BY 聚合无论怎么优化索引都很难比“提前算好”更快。我习惯在业务层维护一张按天/按小时的汇总表或者用一个计数器表保存热门维度的统计值。实时报表入口往往只查汇总表几十毫秒出结果把千万级表留给低频的对账任务去跑。3.4 别让“小动作”变成全表扫描隐式转换、函数包裹和锁放大大表优化的很多收益不是说换个大招而是把那些看着人畜无害的“小动作”揪出来。隐式类型转换是我最常遇到的。比如 user_id 是 varchar 类型SQL 里写 WHERE user_id 123456MySQL 会把字段转换成数字再去比较。对索引而言字段本身被“处理”过了快速定位能力就失效了优化器只能老老实实全表扫。解决方法是让字段类型和查询参数类型完全一致或者把查询参数写成字符串并确保没有转义问题。函数包裹字段也类似。写 WHERE DATE(pay_time) CURDATE() 看着很直观但 pay_time 字段被 DATE 函数包住之后索引上的有序性就用不上了。改成范围条件WHERE pay_time 2024-01-01 00:00:00 AND pay_time 2024-01-02 00:00:00才是让索引发挥作用的正确姿势。范围条件还能触发 ICP进一步减少回表。还有一个大坑是大范围的 UPDATE 和 DELETE。千万级表上执行 UPDATE order_info SET status5 WHERE status1如果 status1 的数据有几百万行这条 SQL 不只是更新数据本身还会因为二级索引维护而锁住大量间隙产生 Gap Lock / Next-Key Lock把周边写入全部堵死。我的处理方案是分批处理先用主键 id 圈定一个小范围比如每次取 5000 个 id更新完提交后再取下一批。这样每条 SQL 锁的行数有限主从延迟也更好控制。这个过程用 pt-archiver 或者自己写存储过程都可以核心思路是“化整为零”。4. 表结构与存储策略字段、行格式和冷热分离4.1 字段设计是把双刃剑从主键到 varchar 都要较真到了一千万行这个级别字段设计已经没有“将就”的余地。主键我建议直接用 bigint 自增或者业务 id 生成器生成的 bigint而不是 varchar。为什么因为 InnoDB 二级索引的叶子节点里都带着主键值主键越长每个二级索引页能容纳的记录越少所有索引都会跟着膨胀。32 位 uuid 字符串做主键的订单表索引体积可以轻松比普通 bigint 多出一倍以上。同理普通字段也要克制用户昵称设 varchar(64) 就够别图省事给 255。varchar 的“最大长度”会影响索引记录在页内的占用字段定多 3 倍索引页装下的行数就少几成查询扫描页数随之增加。另外一个常见的雷是 NULL。建表时尽量就给 NOT NULL DEFAULT 值。NULL 字段在索引里处理起来更笨重统计估算也容易偏掉排序扫描都不太友好。更重要的是业务上的 NULL 往往最后都会变成一堆“空值判断”的查询条件这些条件天然不利于索引选择。text、blob 这种大字段一定要慎防。如果订单备注必须存几十上百行文字建议把它拆到独立的附件表/备注表用 order_id 关联。主表查询的字段集合越短单行数据越紧凑回表和扫描成本都更低。这条规则在千万级表上尤其重要。4.2 表分区对千万级数据大概率不是银弹MySQL 的分区表经常被当成大表优化的首选但我的真实体验是分区表只在特定场景下才有价值别把它当默认方案。分区最擅长的是时间序列数据比如订单表按 pay_time 按月分区。这样业务查询如果总是带上“这个月”“上个月”这样的过滤条件MySQL 可以做分区裁剪只扫描对应分区更关键的是历史数据清理可以直接 DROP PARTITION秒级完成根本不用走 DELETE 再触发大量 binlog 和锁。但如果你把千万级订单表按时间分区而业务查询清一色按 user_id 先过滤那么每次查询都需要去每个分区里跑一遍匹配最后再把多个分区的结果合并。这比普通单表查询更慢因为你把“一个有序结构”拆成了“多个无序结构”。更麻烦的是分区键和索引的关系很微妙主键要么包含分区键要么很多操作会受限维护起来比普通表难得多。我现在的判断标准是如果数据有明确的时间生命周期且清理是刚需分区值得考虑如果表只是“大”而没有清晰的分区键那就先做索引、SQL、归档不要被 8192 个分区上限之类的话题带跑。4.3 冷热分离是性价比最高的“优化”在千万级表优化的所有手段里冷热分离是投入产出比最高的。绝大多数业务表并不是所有数据都被频繁访问比如订单表真正高频查询的是最近一年甚至最近三个月的订单。几年前的历史订单一个月可能才被翻一次而且通常来自后台管理端。我的做法是把订单表按时间切成两张物理表一张是热表 order_info只保留最近一年的数据另一张是 order_info_archive 归档表放更早的历史数据。迁移用 pt-archiver 分批搬比如pt-archiver --source h127.0.0.1,Dyour_db,torder_info \ --where pay_time 2023-01-01 --limit 2000 --bulk-delete \ --dest h127.0.0.1,Dhis_db,torder_info_archivelimit 2000 的意思是每批只搬 2000 行搬完一批自动提交。这样每一条迁移 SQL 都是小事务不会像一次性 DELETE 百万行那样把 undo 撑爆、把主从延迟干到几分钟。迁移之后热表行数从 2600 万降到了 800 万索引体积和缓冲池压力一下子小了很多。热查询的响应时间基本回到“秒回”状态。应用层这边要跟着改。查询接口必须带时间范围程序根据时间判断走热表还是归档表如果实在带不了时间可以在上层维护一个“是否热数据”的标记。千万级数据上我不推荐用视图跨两张表查UNION ALL 的开销在量级上去之后反而更难受应用层显式路由最可控。4.4 在线 DDL 和元数据锁动大表结构的正确姿势索引、归档方案都定下来后最终要执行 ALTER TABLE。千万别小看这一步在千万级表上一次不加参数的 ALTER 可能直接把业务写 hang 住。MySQL 5.7 里 ADD INDEX 默认是 INPLACE 算法但是否允许并发 DML取决于操作类型。最稳妥的是显式声明ALTER TABLE order_info ADD INDEX idx_pay_time(pay_time), ALGORITHMINPLACE, LOCKNONE;ALGORITHMINPLACE 表示不复制整表数据LOCKNONE 表示执行过程中允许并发读写。即便如此执行之前还要查一下有没有长事务在跑。因为 ALTER 在某个阶段必须拿元数据锁MDL如果前面有一个事务迟迟不提交ALTER 就会排队等锁后续所有查询和写入都会被挡在 MDL 之外。看起来像是“一条 ALTER 把库卡死了”其实根源是长事务。8.0 里有些加列操作可以用 INSTANT 算法秒级完成但并非所有 DDL 都支持。如果是超大表要改主键、改字段类型这类必须 rebuild 的操作我建议用 pt-online-schema-change 或 gh-ost。它们的基本原理都是建一张新表通过触发器或 binlog 同步增量追平增量后做原子切换。以前我用 gh-ost 给 5000 万行的业务表改过一列字符集全程没有出现锁等待线上流量完全没受影响。把这个工具放进工具箱是真的能救命。5. 规模再往上走的架构取舍读写分离与分库分表5.1 别急着拆先确认压力到底在哪一层有一个现象我见过很多次单表数据量涨到千万级、接口开始变慢团队第一反应就是“上分库分表”。结果半年后发现架构复杂度上去了分布式事务一堆坑问题却没解决多少。为什么因为原表的问题是慢 SQL 扫全表、索引不合理、大表 JOIN 扫文件这些东西拆成十张表也一样存在。正确的顺序是先回答几个问题QPS 和 TPS 是多少读写比是什么慢查询日志里占大头的是哪些 SQLBuffer Pool 命中率多少如果高并发读是主要矛盾且所有慢点都能通过索引和 SQL 改写解决那架构上一动不如一静。真正该去看读写分离和分库分表的信号是单实例 CPU 已经持续 80% 以上或者热点表的写入已经成为系统瓶颈单纯优化 SQL 再怎么压都压不下来了。5.2 读写分离不是无脑开延迟和路由要处理读写分离是千万级读多写少场景下的首选扩展方案。主库负责写入一两个从库用 MySQL 原生复制同步数据后台查询、报表、统计全打到从库去。这个方案成本低、收益明显但也有坑。最大的坑是主从延迟。MySQL 默认异步复制主库提交完事务、从库可能还没执行完对应的 binlog。如果你的应用刚写完订单就去从库读很可能读不到。规避办法有三种一是对强一致性读的接口做强制主库路由二是把关键写入之后的“立即读”改成小延迟等待三是通过半同步复制或增强半同步把主从延迟压到极低。半同步也不是银弹网络抖动时照样可能退化所以我的原则是能承受延迟的就走从库不能承受的坚决走主库。另一个细节是让从库专门为报表场景建更宽的索引甚至建主库没有的复合索引。反正从库不承担写入压力查询模式固定索引怎么利于报表就怎么来。5.3 分库分表做之前先把分片键想明白当单库单表确实顶不住考虑分库分表时我会先做三件功课缺一件都别动手。第一件确定分片键。分片键必须来自绝大多数查询的必经字段。订单表通常按 user_id 分或者按 order_id 分。按 user_id 分查询必须带 user_id 才能定位到具体分片不带 user_id 的查询就是强制全 shard 扫描中间层把所有分片结果合并复杂度直线上升。第二件设计全局唯一 id。分片之后自增 id 不能再用常见方案是雪花算法或者号段生成器保证各分片生成的 id 全局唯一且趋势递增。第三件想清楚交易型数据怎么处理跨分片事务。两个用户不在同一个分片一笔订单涉及双方账务变动就会变成分布式事务。分布式事务的成本不是“多写两行代码”而是引入协调器、补偿逻辑和一致性兜底复杂度陡增。我的建议是分库分表是最后手段是“优化型手术”。做之前先把单表内部的索引、SQL、归档、读写分离全部榨干。一个千万级表用这些手段往往已经能顶住相当高的并发真正需要分片的通常是数据量大到一两天就产生千万行或者单表索引已经无法支撑业务查询模式的极端场景。5.4 优化不能一劳永逸监控、容量预测和备份恢复大表优化是持续的过程做完一轮不等于以后都不用管。我最后总会把监控搭起来至少保证慢查询日志不断采集、pt-query-digest 定期汇总、锁等待有告警。毕竟业务增长不会停这周 2000 万行半年后就可能是 5000 万行性能基线随时会变。容量预测也很简单用前面建过的基线就能算订单表日增 30 万行平均行大小 1.1KB一天约 330MB一年约 120GB。那就要提前规划磁盘、Buffer Pool 和冷数据迁移周期。经验值是让 Buffer Pool 能装下热表全部索引和部分数据至少 70% 以上否则查询迟早要开始打磁盘。最后提醒一句备份恢复演练别省略。大表优化过程中误删数据、迁移失败、gh-ost 切换异常这些都属于“没遇到是侥幸遇到就要命”的范畴。定期从备份库完整恢复一次确认可用性比任何性能指标都重要。我在实际做这些优化时最深的体会是把优先级排对先看慢日志再改 SQL再调索引再动表结构最后才轮到读写分离和分库分表。这个顺序陪我从 2600 万行的订单表一路把接口压回秒级也帮我躲过了很多次没必要的架构“手术”。如果你手头也有一张正在逼近千万级的表不妨从今天把慢日志打开先看清病根再决定要不要动刀。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询