同一张速查贴,这回从问号底下往上翻。
场景没动:MySQL 5.7、app_db、三百一十一万行的 t_order,报表页查询同时压了 status 和 channel 两个等值条件。
idx_status 和 idx_channel 两棵单列索引都建好了,EXPLAIN 丢出来却是:
sql
`EXPLAIN select id from t_order
where status = 1 and channel = 'app';
`
+------+------------------------+------+---------+-------------+
| type | possible_keys | key | rows | Extra |
+------+------------------------+------+---------+-------------+
| ALL | idx_status,idx_channel | NULL | 3089466 | Using where |
+------+------------------------+------+---------+-------------+
第一反应是索引坏了,回头才想明白要分两步看。
报名和判状是两回事,possible_keys 只负责签到,两棵都到了。
key 那栏空着,说明两棵都被验过又都被放下。
要弄清为什么放下,先量 status 这列值不值一条独立索引,一条聚合就够:
sql
`select count(*) as total,
count(distinct status) as d_status,
sum(status = 1) as s1
from t_order;
`
+---------+----------+---------+
| total | d_status | s1 |
+---------+----------+---------+
| 3112401 | 4 | 1037467 |
+---------+----------+---------+
去重只剩 4 个值摊在 3112401 行上,这个比值小得几乎贴地。
status=1 单独占掉 1,037,467 行,整表三分之一都压在同一个值上。
distinct 除以总数越靠近 1,单列索引才越站得住。
channel 那边用 group by 把分布摊开看:
sql
`select channel, count(*) from t_order
group by channel order by 2 desc;
`
+---------+----------+
| channel | count(*) |
+---------+----------+
| app | 1936412 |
| h5 | 408331 |
| pc | 355120 |
| mini | 267782 |
| wx | 144756 |
+---------+----------+
app 一档 1936412 行,吃掉六十二个百分点,两个条件交叉之后我数出六十四万五千上下。
把优化器那本账替它演一遍就通了:走 idx_status 要回表一百零四万次,捞回来还得扔掉将近四十万行。
改走 idx_channel,回表笔数直接奔到一百九十四万。
行宽按 1.3K 摊匀整表四个 G 出头,顺序读本来就是全表扫的看家本事。
不服气塞过一个 FORCE INDEX:
sql
`select id from t_order force index (idx_status)
where status = 1 and channel = 'app';
`
低峰各跑三遍取均值,老实全表扫 25 秒,强拧那版 41 秒。
这种写法只配躺在验证稿里,生产语句一旦落下 FORCE INDEX,分布挪一挪雷就响。
那什么时候才算真的该走没走?灌数据那晚给了个现形记。
同一条按 channel 加 create_time 的查询,起初 rows 读数是 31277:
sql
`EXPLAIN select id from t_order
where channel = 'h5'
and create_time > '2026-09-20 00:00:00';
`
+-------------+-------+
| key | rows |
+-------------+-------+
| idx_channel | 31277 |
+-------------+-------+
计划押在三万行上,可那一档实际早冲到四十多万。
sql
`analyze table t_order;
`
+----------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+----------------+---------+----------+----------+
| app_db.t_order | analyze | status | OK |
+----------------+---------+----------+----------+
+------+----------+
| key | rows |
+------+----------+
| NULL | 408331 |
+------+----------+
analyze 之后 rows 跳到 408331,翻的量级不止一档,计划跟着变脸回到全表扫那队。
5.7 的持久化统计有约 10% 改动量才触发自动重算的门槛,大批量灌完之后它常常还愣着,手动补一遍 analyze 催醒就行。
or 逢索引必废这句老话几分真?一条 EXPLAIN 就能摆平:
sql
`EXPLAIN select id from t_order
where status = 3 or channel = 'mini';
`
+-------------+------------------------+------------------------------------------------+
| type | key | Extra |
+-------------+------------------------+------------------------------------------------+
| index_merge | idx_status,idx_channel | Using union(idx_status,idx_channel); Using where |
+-------------+------------------------+------------------------------------------------+
两侧各有索引可托,5.7 真会去并,type 都写成 index_merge 了。
Extra 行头 Using union 后边点着两棵树的名,赖不掉。
把我踹回全表的是另一句:or 里掺进一列没索引的,一侧无人签到并集当场散伙。
三个问号销完这张贴才算贴牢,可 EXPLAIN 拍到的全是静态帧,线上偶发那几秒始终没蹲着现行。
下一张贴,先把慢日志架起来守株。