存储过程本身不决定索引使用,真正起作用的是其内部SQL语句执行时优化器对基表的选择;需确保参数类型一致、满足最左前缀原则、避免函数/隐式转换,并通过EXPLAIN验证type、key和Extra字段。

存储过程本身不决定走不走索引——真正起作用的是它内部那条具体 SQL 语句在执行时,优化器对基表的选择。所谓“不走索引”,90% 的情况是某条 SELECT、UPDATE 或 DELETE 没触发索引查找,而不是存储过程写法有问题。
EXPLAIN 看到 type=ALL 或 key=NULL 就基本确认没走索引
别靠猜,直接把存储过程中最可疑的 SQL 拎出来,用 EXPLAIN 跑一遍。注意必须满足两个前提:
- 参数值和存储过程调用时完全一致(比如传的是
'2026-07-01',就不能换成NOW()) - 会话环境一致(
SQL_MODE、字符集、collation_connection都要一样)
重点关注三处:
-
type是ALL或index:说明在全表或全索引扫描,不是有效利用索引 -
key是NULL:压根没选上任何索引 -
Extra出现Using filesort、Using temporary:往往意味着 WHERE 或 ORDER BY 没借上索引能力
参数类型不匹配导致隐式转换,索引直接失效
这是存储过程中最高频的坑。比如字段是 BIGINT,但你定义了 IN p_id VARCHAR(32),然后写 WHERE user_id = p_id —— MySQL 会悄悄转成 CAST(p_id AS SIGNED),索引就废了。
- 验证方法:在过程里加一句
SELECT @p_id := p_id;,再跑EXPLAIN SELECT * FROM t WHERE id = @p_id;,对比直接用p_id的结果 - 解决办法:显式转换,比如
WHERE user_id = CONVERT(p_id, UNSIGNED),或者干脆把参数类型改成和字段一致 - Oracle/SQL Server 同理:
NUMBER参数传入字符串、VARCHAR2参数查CHAR字段,都会触发隐式转换
复合索引顺序错位或只用右列,索引形同虚设
建了 INDEX idx_status_time (status, create_time),但在存储过程里只写 WHERE create_time > '2026-08-01',这个索引完全不会被用到。
- 复合索引生效的前提是满足“最左前缀原则”:要么
WHERE status = ?,要么WHERE status = ? AND create_time > ?,单独查create_time不行 - 如果过程里有多种查询模式(比如有时查
status,有时查user_id),别指望一个索引全包圆;优先覆盖高频 + 高选择性组合,低频分支考虑拆语句或加OPTION (RECOMPILE) - MySQL 8.0+ 支持函数索引,但得确保存储过程里写的表达式和索引定义完全一致,比如索引是
INDEX idx_upper_name ((UPPER(name))),那 WHERE 就得写UPPER(name) = 'ABC'
动态 SQL 和绑定变量让 EXPLAIN 失效,真实执行路径藏得深
用 CONCAT 拼接 SQL 再 EXECUTE IMMEDIATE,或者 PL/SQL 里用 EXECUTE IMMEDIATE ... USING,EXPLAIN PLAN FOR 看不到真实计划——它只是预估,不代入实际参数值。
- Oracle 必须开 SQL Trace(
DBMS_MONITOR.SESSION_TRACE_ENABLE)抓真实的执行计划 - SQL Server 可查
sys.dm_exec_query_stats关联sys.dm_exec_sql_text,找execution_count高但total_logical_reads异常大的语句 - MySQL 可开启
slow_query_log并设long_query_time=0,配合pt-query-digest抓出慢语句原文再 EXPLAIN
最易被忽略的一点:即使索引存在、写法看着没问题,如果 SHOW INDEX FROM table_name 里 Cardinality 远低于实际行数,说明统计信息陈旧,优化器瞎猜——这时候 ANALYZE TABLE 比改 SQL 更管用。










