您观察到的现象是完全正确的 :同一个会话中使用Prepare语句,两次执行使用了不同的执行计划(第一次全表扫描,第二次索引扫描)。
这是因为:
MySQL的Prepare只缓存解析树,不缓存执行计划
每次EXECUTE时,优化器都会基于 当前参数值 重新进行优化
这是MySQL设计的 查询优化策略 ,不是bug
让我们分析您的测试数据:
-- 表中数据分布:-- status='N': 100万行 (绝大多数)-- status='Y': 10行 (极少数)-- 第一次执行:WHERE status < 'Y'-- 'Y'的ASCII值比'N'大,所以 status<'Y' 会包含所有'N'的记录-- 预计返回约100万行 => 优化器选择全表扫描-- 第二次执行:WHERE status < 'N'-- 'N'的ASCII值与'N'相等,所以 status<'N' 不包含任何记录-- 预计返回0行 => 优化器选择索引扫描
我们可以创建更简单的测试:
-- 准备语句PREPARE stmt FROM 'SELECT * FROM orders WHERE status = ?';-- 第一次:查询'N'(100万行)SET @status = 'N';EXECUTE stmt USING @status;-- 执行计划:全表扫描(因为返回大部分数据)-- 第二次:查询'Y'(10行)SET @status = 'Y';EXECUTE stmt USING @status;-- 执行计划:索引扫描(因为返回少量数据)
您对比得很准确:
| 数据库 | 计划缓存机制 | 触发条件 |
|---|---|---|
| MySQL | 无计划缓存 (InnoDB) | 每次EXECUTE都重新优化 |
| Oracle |
软解析(shared pool)
软软解析(session cached cursor) |
3次执行相同SQL |
| PostgreSQL | session plan cache | 5次执行相同结构SQL |
| SQL Server | 全局计划缓存 | 第一次执行后缓存 |
MySQL的例外情况 :
SQL Server的链接服务器 :某些版本可能有简单缓存
prepare_optimizer_cost参数 :MySQL 8.0.29+新增,但默认关闭
SQL文本完全相同 :不是prepare,而是完全相同的SQL可能复用
使用
EXPLAIN
验证:
-- 准备PREPARE stmt FROM 'SELECT * FROM orders WHERE status < ?';-- 第一次:全表扫描SET @status = 'Y';EXPLAIN FOR CONNECTION connection_id();-- 或使用普通EXPLAIN:EXPLAIN SELECT * FROM orders WHERE status < 'Y';-- 第二次:索引扫描SET @status = 'N';EXPLAIN FORMAT=JSON EXECUTE stmt USING @status;-- 会显示不同的执行计划
性能考虑 :
数据倾斜场景 :如果缓存了第一个计划(全表扫描),后续查询少量数据时性能灾难
自适应优化 :MySQL优化器能根据实际数据分布选择最优计划
内存效率 :不缓存计划可以节省内存,特别是短连接场景
但这也带来问题 :
每次执行都有优化器开销
参数嗅探可能导致不稳定执行计划
高并发场景下CPU消耗较大
如果您想强制使用特定执行计划:
-- 1. 使用FORCE INDEXPREPARE stmt_force FROM 'SELECT * FROM orders FORCE INDEX(idx_status) WHERE status < ?';-- 2. 使用IGNORE INDEXPREPARE stmt_ignore FROM 'SELECT * FROM orders IGNORE INDEX(idx_status) WHERE status < ?';-- 3. 使用优化器提示PREPARE stmt_hint FROM 'SELECT /*+ NO_INDEX(orders) */ * FROM orders WHERE status < ?';
MySQL 8.0有一些相关改进:
-- 查询prepare相关变量SHOW VARIABLES LIKE '%prepared%';-- prepared_stmt_count: 当前缓存的prepare语句数量-- max_prepared_stmt_count: 最大限制-- 自适应哈希索引SHOW ENGINE INNODB STATUS\G-- 可以查看自适应哈希索引的使用情况
您的测试结论是正确的: MySQL的Prepare语句在同一个会话内,执行计划不会被缓存和复用 。每次EXECUTE都会:
使用缓存的解析树(避免语法解析)
根据当前参数值重新进行优化器评估
生成新的执行计划
这与Oracle的"软解析/软软解析"有本质区别。MySQL这样设计是为了保证每次查询都能获得最适合当前参数值的执行计划,避免因数据分布不均导致的性能问题。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/7655508/viewspace-3171588/ ,如需转载,请注明出处,否则将追究法律责任。