SELECT *在百万级表中特别危险,不仅因冗余字段增加传输与内存开销,更因必然回表导致多次随机IO,且易引发全表扫描(EXPLAIN中type=ALL),应明确指定字段并确保其落在同一覆盖索引中。
为什么SELECT *在百万级表里特别危险
它不只是多传几个字段的事——数据库要读取整行数据、解压、序列化、网络传输、客户端内存分配,每一步都成倍放大延迟。更关键的是,如果没走覆盖索引,select *必然触发回表,让原本一次索引扫描变成多次随机io。
实操建议:
- 永远用明确字段代替
*,比如SELECT user_id, order_status, create_time - 确认这些字段是否全在同一个复合索引里,如果是,就构成覆盖索引,避免回表
- 对只读报表类查询,考虑加
SQL_NO_CACHE(MySQL 5.7+)或禁用查询缓存,防止缓存污染拖慢其他查询
EXPLAIN输出里type=ALL意味着什么
这代表全表扫描,百万级表上基本等于“等一会儿”。但别急着建索引——先看key列是否为NULL,再检查possible_keys有没有候选索引。很多情况下是WHERE条件用了函数或类型隐式转换,导致索引失效。
常见掉坑点:
-
WHERE DATE(create_time) = '2023-01-01'→ 改成WHERE create_time >= '2023-01-01' AND create_time -
WHERE user_id + 0 = 12345→ 直接写WHERE user_id = 12345 - 关联字段类型不一致,比如
INT连VARCHAR,MySQL会自动转类型,索引就废了
复合索引的顺序到底该怎么排
不是按WHERE里出现的先后,而是按选择性(distinct值占比)从高到低。比如user_id有100万不同值,order_status只有5个状态,那INDEX(user_id, order_status)能命中WHERE user_id = ? AND order_status = ?,反过来就不行。
还要兼顾查询模式:
- 如果常查
WHERE user_id = ? ORDER BY create_time DESC,索引应为INDEX(user_id, create_time) - 如果还有
LIMIT分页,且偏移量大(如LIMIT 10000, 20),优先考虑用游标分页:WHERE create_time - 别忘了
DROP INDEX旧索引——重复或冗余索引会拖慢写入,还干扰优化器选错执行计划
Navicat里怎么快速验证索引效果
别只看执行时间,Navicat的查询分析器(Query Analyzer)能暴露真实瓶颈。打开它后重点盯三个地方:Total Time(总耗时)、Rows Examined(扫描行数)、Rows Sent(返回行数)。理想情况是后者远小于前者,且Rows Examined接近你要的结果数。
操作要点:
- 右键查询窗口 → “解释”(Explain)直接看执行计划,不用手敲
EXPLAIN - 在“工具”菜单启用“查询分析器”,它会自动抓取慢查询日志和
performance_schema数据 - 注意“前5个最耗时查询”面板——如果同一语句反复上榜,说明索引没生效或数据分布突变(比如某
status值突然占90%)
order_status从5个值变成50个,或某用户订单暴增百倍,原来高效的索引可能立刻退化。定期用ANALYZE TABLE更新统计信息,比盲目加索引更重要。











