高负载下ASP.NET读写SQL Server NVARCHAR(MAX)列遇LOB异常求助
核心结论
确实存在SQL Server在特定场景下对NVARCHAR(MAX)这类LOB列的快照隔离保护出现异常的情况,尤其在并发交替更新LOB列的长短值时,LOB数据的版本链处理逻辑可能出现漏洞,导致读取错误或数据不一致。
问题根源
LOB列的存储转换触发版本链异常
NVARCHAR(MAX)会根据长度自动在**行内存储(in-row)和行外存储(out-of-row)**之间切换:短字符串存在行内,长字符串溢出到行外存储。当并发交替修改长短值时,行内/行外的转换过程中,SQL Server的快照版本链可能没有正确关联LOB数据的新旧版本,导致后续读取拿到无效的LOB指针或混杂的版本数据。快照隔离下LOB版本的特殊处理逻辑
快照隔离依赖行版本存储,但LOB列的行外数据版本管理和普通列不同。即使你通过(UPDLOCK, HOLDLOCK)锁定了行,后续无提示的SELECT读取快照版本时,LOB数据的版本可能未被正确标记为当前事务的修改版本,导致读取到其他事务的LOB数据,或者因版本指针无效抛出A read operation on a large object failed while sending data to the client错误。SQL Server 2022的潜在bug
尽管SQL Server 2022是较新版本,但针对LOB列在快照隔离下并发交替更新的场景,可能存在未完全修复的版本链处理漏洞,导致数据混淆或读取失败。
可行解决方案
强制LOB列行外存储
修改表定义,强制NVARCHAR(MAX)列始终存储在行外,避免行内/行外转换带来的版本问题:ALTER TABLE [目标表名] ALTER COLUMN [LOB列名] NVARCHAR(MAX) WITH (LOB_DATA_STORAGE = OFF_ROW);调整后续SELECT的锁定提示
在最后一步的SELECT中添加锁定提示,强制读取当前事务修改后的最新数据,绕过快照版本的LOB问题:SELECT [LOB列名] FROM [目标表名] WHERE [主键列] = @Id WITH (REPEATABLEREAD);或在同一事务内使用
(UPDLOCK),确保读取到自身修改的版本。确保操作在同一事务内执行
确认整个SELECT(带锁)-> UPDATE -> SELECT流程处于同一个数据库事务中,避免因事务边界拆分导致后续SELECT读取到其他事务的快照版本。安装SQL Server 2022最新累积更新
检查微软发布的SQL Server 2022最新累积更新(CU),此类LOB快照隔离的bug可能已被修复,更新后可解决问题。
内容的提问来源于stack exchange,提问作者Jørn Wildt

