当前位置:首页 > 综合

sqlserver实战高清

上周帮一个朋友排查问题,战高他的战高订单表在促销活动期间查询慢到离谱,一个简单的战高按时间范围拉取数据的语句跑了将近40秒。我让他把执行计划发过来一看,战高好家伙,战高全表扫描,战高索引压根没走。战高更离谱的战高是,那张表上明明建了索引。战高

这种情况我遇到过太多次了。战高SQL Server 的战高查询优化器不是万能的,它有时候会做出让人摸不着头脑的战高选择。比如统计信息过期了,战高优化器基于旧数据估算出行数只有几百条,战高觉得走聚集索引扫描更划算,战高结果实际数据量是几百万行。你看着执行计划里那个粗粗的箭头,就知道大事不妙。

索引不是建了就完事

很多人建索引的逻辑是:这个字段经常出现在 WHERE 里,加个索引。听起来没毛病,但忽略了几个关键点。复合索引的字段顺序决定了它能不能被有效利用。比如你建了 (Status, CreateTime) 的索引,查询条件是 WHERE CreateTime > '2024-01-01',这个索引基本用不上,因为前导列 Status 没出现在条件里。SQL Server 可能会做索引扫描而不是查找,效果跟全表扫描差不了太多。

我一般会建议把选择性最高的字段放在复合索引的最前面。但也不是绝对的,得看具体查询模式。如果 Status 只有三四个值,CreateTime 跨度又大,那 CreateTime 放前面更合理。这事儿没有万能公式,得看数据分布。

那个让人又爱又恨的 NOLOCK

说到实战,不得不提 NOLOCK 这个提示。很多开发人员把它当万能药,哪里查询慢就往哪里加。加了之后确实快了,因为不等待锁了,直接读。但代价是可能读到脏数据,甚至出现数据重复或丢失的情况。我见过一个报表系统,因为大量使用 NOLOCK,月底对账时数字对不上,财务那边差点报警。

读已提交快照隔离(RCSI)是更优雅的方案。开启之后,读操作不会阻塞写操作,写操作也不会阻塞读操作,而且读到的是一致性快照。代价是 tempdb 的压力会增大。对于大多数 OLTP 系统来说,这个代价是值得的。改一行数据库配置就能解决大量锁等待问题,比到处加 NOLOCK 靠谱得多。

参数嗅探:同一个存储过程,两种命运

有个案例我印象特别深。一个存储过程,传不同的参数值,执行时间能从200毫秒跳到15秒。原因就是参数嗅探。SQL Server 在第一次执行时根据传入的参数值生成了执行计划,然后把这个计划缓存起来。后面再来一个数据分布完全不同的参数值,它还是用旧计划,结果就是灾难。

解决办法有好几种。可以用 OPTION (RECOMPILE) 让每次执行都重新编译,适合执行频率不高但参数变化大的场景。也可以用 OPTIMIZE FOR UNKNOWN 让优化器用平均分布来估算。还有一种做法是把参数赋值给局部变量,这样优化器就嗅探不到了,但可能生成更差的计划。没有银弹,得根据实际情况选。

别忽视等待统计

性能问题不一定出在查询本身。我习惯先看一眼等待统计,sys.dm_os_wait_stats 这个 DMV 能告诉你实例到底在等什么。如果 CXPACKET 排在最前面,说明有并行查询在捣乱。如果 PAGEIOLATCH_SH 很高,那可能是磁盘 I/O 瓶颈。如果 LCK_M_X 突出,那就是锁阻塞问题。

有一次我遇到一个系统,CPU 一直跑满,查询本身都不慢,但整体吞吐量上不去。查等待统计发现 SOS_SCHEDULER_YIELD 异常高,说明有大量 CPU 密集型操作在争抢调度。最后定位到一个标量函数,在查询里被逐行调用了几百万次。改成内联表值函数之后,CPU 直接降了一半。

SQL Server 的性能调优,说到底就是理解它怎么思考、怎么执行、怎么等待。工具都在那里,DMV、执行计划、扩展事件,关键是你得知道什么时候该看哪个。经验这东西,踩的坑多了自然就有了。

分享到: