上周去一家创业公司做技术交流,CTO 拉着我问了个挺实在的问题:数据库跑得越来越慢,加内存、换 SSD 都没用,到底该从哪下手?这问题我太熟了。做数据库优化的,十有八九都掉进过“硬件万能论”的坑。其实 SQL Server 的瓶颈,绝大多数时候跟 CPU、内存关系不大,问题出在查询语句和索引设计上。

先说个最典型的场景。很多开发人员写 SQL 的习惯是直接从 ORM 框架里复制粘贴出来。比如一个简单的订单查询,他们会写 。这看起来很合理,对吧?但问题大了去。YEAR 函数包裹了 CreateDate 列,导致索引完全失效,SQL Server 被迫做全表扫描。一张百万级订单表,查询时间从几十毫秒直接飙到几秒。解决办法很简单:改成 。索引能正常使用,性能天差地别。
索引设计这块,很多人有个误区:索引越多越好。我见过一张表上挂了十几个索引,每个都有独立用途,但写入性能一塌糊涂。索引不是越多越好,而是越精准越好。比如用户表经常按邮箱查询,那就建一个覆盖索引:。这样查询时只需要访问索引页,不用回表取数据。删掉一个冗余索引,写入性能能提升 20% 以上。关键是要用 这个动态管理视图,找出那些几乎不用的索引,果断删掉。
再说个容易被忽略的点:统计信息。SQL Server 的查询优化器靠统计信息来估算行数。如果统计信息过期,优化器会生成错误的执行计划。比如一张 10 万条数据的表,统计信息显示只有 1000 条,优化器就会选择全表扫描,结果查询慢成狗。解决方案是定期更新统计信息,特别是数据变动频繁的表。可以用 命令,或者把数据库的自动更新统计信息阈值调得更敏感。默认是 20% 的数据变动才触发更新,对大表来说,这个阈值太宽松了。
参数嗅探也是个坑。存储过程第一次执行时,SQL Server 会根据传入的参数值生成执行计划。如果第一次传了个特殊参数,查到的数据极少,优化器会生成适合小数据量的计划。后面传了正常参数,查到的数据量很大,这个计划就不适用了,性能直接崩掉。解决方法是使用 提示,或者用 让优化器使用平均分布的统计信息。不过要小心, 会增加编译开销,适合执行频率低但数据分布极不均匀的查询。
事务隔离级别也经常被忽视。默认的 READ COMMITTED 在 SQL Server 里使用共享锁,读操作会阻塞写操作。高并发场景下,这就成了性能杀手。改成 READ COMMITTED SNAPSHOT 隔离级别,读操作不再需要锁,只读取版本数据,写操作不会被阻塞。代价是 tempdb 的压力会增大,需要额外空间存储行版本。但这个代价通常值得。淘宝双十一的数据库就用了这个隔离级别,并发能力提升明显。
硬件层面的优化其实没那么玄乎。加内存确实能缓解一些压力,但前提是 SQL Server 能用到这些内存。检查一下 的设置,很多人装完系统就忘了配置,SQL Server 默认会吃掉所有可用内存,导致操作系统和其他应用没有内存,反而拖慢整体性能。正确做法是给操作系统留 2‑4 GB 内存,剩下的分配给 SQL Server。另外,日志文件和数据文件要放在不同的物理磁盘上,减少 I/O 竞争。日志文件按 64 MB 增长,不要用默认的 10%,避免频繁扩展造成性能抖动。
说个实操小技巧。遇到慢查询,先别急着翻代码。打开 SQL Server Management Studio,右键点击问题数据库,选择 “报表 → 性能 → 按总 CPU 时间排名的查询”。这个报表会直接告诉你哪些查询最耗资源。然后针对这些查询,执行 和 ,查看逻辑读取次数。如果逻辑读取次数很高,说明索引有问题;如果逻辑读取少但实际执行慢,可能是阻塞或等待导致的。用 看当前在等待什么资源,对症下药。数据库优化不是玄学,而是一步一步排查出来的。


