为什么T-SQL的SELECT查询会产生三个未关闭事务并引发阻塞?
T-SQL查询包含varbinary(max)列引发未关闭事务及阻塞问题解答
核心原因说明
你观察到的现象本质是隐式事务配置 + LOB(大对象)列读取的特殊内部机制共同作用的结果,三个未关闭事务的来源和查询挂起的原因如下:
- 首先确认SSMS的
IMPLICIT_TRANSACTIONS配置:如果该选项被开启,任何DML语句(包括SELECT)执行时都会自动开启显式事务,必须手动执行COMMIT/ROLLBACK才会释放。你可以执行DBCC USEROPTIONS查看Implicit Transactions项的状态,确认是否为ON。 varbinary(max)属于LOB类型,当数据大小超过8KB时会存储在独立的LOB分配单元,采用三层B树结构(根节点、中间节点、叶子节点)存储。你在WHERE条件中直接匹配LOB完整二进制值时,SQL Server需要逐行读取LOB内容比对,这个过程会为B树三层节点的读取分别创建内部一致性事务,这就是你看到三个未关闭事务的来源。- 即使添加
NOLOCK提示(即READ UNCOMMITTED隔离级别)也无法避免阻塞:NOLOCK只能跳过常规数据页的行共享锁,对LOB存储的内部一致性锁不生效,LOB比对的长耗时会导致这些内部事务长期持有锁,引发其他会话阻塞。
解决方案
- 关闭隐式事务:执行
SET IMPLICIT_TRANSACTIONS OFF后再运行查询,查询结束后主动执行COMMIT清理残留事务。- 优化LOB查询逻辑:新增持久化哈希计算列,示例如下:
ALTER TABLE JobImage ADD ImageHash AS HASHBYTES('SHA2_256', [Image]) PERSISTED; CREATE NONCLUSTERED INDEX IX_JobImage_ImageHash ON JobImage(ImageHash);查询时先匹配哈希值,再匹配LOB值,可将查询耗时从分钟级降至毫秒级,避免长时间锁持有。
3. 启用快照隔离:在数据库级别开启READ_COMMITTED_SNAPSHOT选项,使用行版本化读取避免持有共享锁,降低阻塞概率。
内容的提问来源于stack exchange,提问作者Roger Allen
相关产品推荐
相关产品推荐

