select * 在存储过程中伤性能因执行计划难复用、增加传输与内存压力、易引发隐式错误,且优化器跳过列裁剪,连带加载无用大字段;应显式指定字段。

为什么 SELECT * 在存储过程中特别伤性能
因为存储过程常被高频调用,而 SELECT * 会让执行计划无法复用、增加网络传输和内存压力,还容易因表结构变更引发隐式错误。更关键的是,sql Server 和 mysql 的查询优化器在面对 * 时,往往跳过列裁剪优化,连带加载大量无用字段(比如 TEXT、json 或大 BLOB)。
实操建议:
- 显式列出所需字段,哪怕多打几下键盘——尤其避免在游标或临时表插入时写
SELECT * - 如果用到了
INSERT INTO #tmp SELECT *,先查清源表字段顺序和类型,改成INSERT INTO #tmp (col1, col2) SELECT col1, col2 FROM ... - 在 SQL Server 中,用
SET STATISTICS IO ON对比逻辑读差异;MySQL 可看EXPLAIN format=JSON里的used_columns字段
WITH RECOMPILE 不是万能缓存开关
很多人以为加了 WITH RECOMPILE 就能“解决参数嗅探”,结果反而让每次执行都硬编译,CPU 直线上升。它真正适用的场景极少:比如报表类存储过程,输入参数范围极广(@date_from 跨十年 vs 跨一天),且数据分布严重倾斜。
实操建议:
- 优先尝试
OPTION (RECOMPILE)加在具体语句后,而非整个过程加WITH RECOMPILE—— 精准控制,减少编译开销 - SQL Server 2016+ 可启用
QUERY_OPTIMIZER_HOTFIXES或开启自动参数化,比硬加重编译更稳 - MySQL 存储过程中没有等效机制,得靠应用层拆分逻辑,或用
PREPARE/EXECUTE动态构造语句
临时表 vs 表变量:别只看语法简洁度
表变量(DECLARE @t table(...))看着干净,但 SQL Server 不会为它生成统计信息,优化器默认按 1 行估算;而临时表(CREATE TABLE #t)有统计信息、可建索引、支持事务回滚——但也会触发 tempdb 争用。
实操建议:
- 记录数预估 > 100 行,一律用
#t;若只是存 2–3 个 ID 做过滤,@t更轻量 - 在 SQL Server 中,给
#t加索引要趁早:CREATE INDEX ix ON #t(col) WHERE col IS NOT NULL(注意 WHERE 条件能帮优化器跳过空值扫描) - MySQL 没有表变量,只能用
CREATE TEMPORARY TABLE,但要注意它不支持外键、全文索引,且会话断开即删
WHERE 条件里写函数,等于主动放弃索引
像 WHERE YEAR(order_date) = 2024 或 WHERE UPPER(name) = 'ABC' 这类写法,在绝大多数引擎里都会让索引失效。不是“可能不走”,而是优化器明确知道无法做索引 Seek,直接降级为 Scan。
实操建议:
- 把函数移到右侧:用
WHERE order_date >= '2024-01-01' AND order_date 替代 <code>YEAR() - 需要大小写无关匹配?SQL Server 可建计算列 + 索引:
ALTER TABLE t ADD name_upper AS UPPER(name),再在该列建索引 - MySQL 8.0+ 支持函数索引:
CREATE INDEX idx_name_up ON t ((UPPER(name))),但注意括号必须是双层
最常被忽略的一点:存储过程性能问题,八成出在第一次执行之后的执行计划缓存上。不是代码写得慢,是缓存了一个不适合当前参数的计划,又没被及时踢掉。盯住 sys.dm_exec_query_stats 里的 execution_count 和 last_elapsed_time,比反复改代码更管用。