13年模块开发血泪:B+树索引竟成搜索毒瘤
发布时间:2026-10-09 03:53:01 所属栏目:搜索优化 来源:DaWei
导读:凌晨三点十四分,报警群连炸了八条消息,订单查询平均响应飙到四千三百毫秒,而我面前那杯美式已经凉透。我打开 Grafana 面板,QPS 只有两百一十左右,慢查询日志里躺着一条 `SELECT FROM ord_mast WHERE stat = 'INIT' AND c
|
凌晨三点十四分,报警群连炸了八条消息,订单查询平均响应飙到四千三百毫秒,而我面前那杯美式已经凉透。 我打开 Grafana 面板,QPS 只有两百一十左右,慢查询日志里躺着一条 `SELECT FROM ord_mast WHERE stat = 'INIT' AND ctime BETWEEN ? AND ? LIMIT 5000;`,执行计划 text 显示 key 是 idx_stat_ctime,rows 预估为八十九万,Extra 里赫然写着 Using index condition; Using where。 这破索引根本就是披着羊皮的全表扫描。 运维老李昨天下午五点半拍着桌子冲我喊,"你们模块把 B+树当万金油,越用越毒"。他手里还攥着半张打印出来的 iostat,说 sdb 的 util 已经干到 98% w_await 两百三十毫秒。 我上个月十五号用 pt-query-digest 跑了一遍昨天全量日志,那条查初始状态订单的语句占总慢查的六成七,扫描行数三百七十二万,返回不到四千行。磁盘吞叶节点一层层翻,二级索引回表像拿勺子舀海水。 二〇一一年我在做单体版进销存时,单表最多二十一万行,给 status 字段加个普通索引,`WHERE status='NEW'` 平均零点几毫秒就出结果,那时候 MySQL 5.5 的 innodb_buffer_pool 才给了八百兆,照样跑得飞起。 现在这套系统 order 表带 stat_ctime 联合索引,数据八百多万,每次范围捞 INIT 状态跟趟沼泽一样。
文章配图,仅供参考 索引 cardinality 低得简直羞辱人。我前天下午四点零二分用 `SHOW INDEX FROM ord_mast;` 看基数,stat 字段只有五个 distinct 值,idx_stat_ctime 的 Cardinality 显示为一百四十万,可实际 DISTINCT stat 才五。优化器选它只是因为没别的了。 一九年我维护 CRM 时遇到过类似坎,那时我用 force index(create_time) 加程序侧按天分片硬拆,慢查从两秒压到一百八十毫秒。 这次我干脆在 DAO 层把 stat='INIT' 改成按 ctime 倒序走主键扫描,配合 `innodb_change_buffering=all` 和把 `tmp121_tb` 这个遗留临时表迁移走,平均响应回到两百六十毫秒上下。 B+树在低选择性字段上就是毒瘤,堪称慢性自杀式设计。 昨天周二上午十点碰评审,王工翻着建表脚本说,"别再给状态字段建头一列索引了,我们上次上线 MySQL 8.0.32 后忘了删 idx_flag,导致 insert 带二级索引维护多花零点四毫秒,两百并发下写堆积到 socket timeout 返回 502"。他说这话时在白板上画了个歪歪扭扭的树,叶子节点全涂黑。 我用 sysbench 在测试库回放 reproduce 了,写场景返回的 error 码里有 1205 lock wait timeout 和一堆 502,平均延迟 2.3 秒,TPS 从一千三掉到两百八。 范围查找 löschen 掉那个联合索引之后,世界安静了。 我昨天夜里十一点半登到跳板机,执行 `ALTER TABLE ord_mast DROP INDEX idx_stat_ctime;`,随后 tail -f slow.log,看到那条语句不再出现,取而代之是一条走 PRIMARY 的相近行数扫描在五十八毫秒收工。 一味相信 B+树万能的人绝对没被 cardinality 打过脸。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
站长推荐


PHP多人模块开发原理解析
