两个等值条件摆着,MySQL 偏要全表扫

同一张速查贴,这回从问号底下往上翻。

场景没动: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 拍到的全是静态帧,线上偶发那几秒始终没蹲着现行。

下一张贴,先把慢日志架起来守株。

相关推荐
Oll Correct18 分钟前
Adobe illustrator 案例十四:花元素标志草图绘制
笔记·ui·adobe·illustrator
每月一号准时摆烂5 小时前
服务器运维笔记(一)——使用shell编写实时查看BMC功率的脚本
运维·服务器·笔记
深圳智根网络程序开发5 小时前
WordPress固定链接怎么设?改不对谷歌收录直接崩
经验分享
德思特5 小时前
如何提升 5G 移动性切换验证效率?德思特 Amarisoft Callbox 新增 NR SA LTM 测试功能详解
经验分享·无线通信·车载通信
虹科数字化与AR8 小时前
农业机械AR质检ROI怎么算?从搬运、测量、返工和交付成本入手
经验分享·ar·质检·农业机械
聪明的笨猪猪21 小时前
Java 工程案例:AI 生成 @Async 异步线程池踩坑
java·经验分享·面试·ai编程
柳鲲鹏1 天前
内斗学概论:43、平级内斗
笔记
星恒随风1 天前
MySQL 从零入门(四):表的增删改查与基础查询详解
linux·服务器·数据库·笔记·mysql
Z21-X1 天前
云原生运维学习笔记:ECShop全链路容器化部署与Docker底层资源隔离深度剖析
运维·笔记·学习·云原生