使用 SQL Server 锁定的 DataReader 行为

Posted

技术标签:

【中文标题】使用 SQL Server 锁定的 DataReader 行为【英文标题】:DataReader Behaviour With SQL Server Locking 【发布时间】:2009-10-08 15:48:53 【问题描述】:

当大型数据集通过DataReader 从 SQL 服务器查询返回时,我们的数据层遇到了一些问题。当我们使用DataReader 填充业务对象并将它们序列化回客户端时,获取可能需要几分钟(我们正在向用户显示进度:-)),但我们发现有一些非常核心对受影响的表进行锁定,导致其他更新被阻止。

所以我想我有点幼稚的问题是,由于执行查询而取出的锁实际上在什么时候被放弃了?我们似乎发现锁一直存在,直到DataReader 的最后一行被处理并且DataReader 实际上已关闭——这似乎正确吗?关于 DataReader 如何在幕后工作的快速 101 会很棒,因为我一直在努力寻找任何合适的信息。

我应该说我意识到锁定问题是主要问题,但我只关心DataReader 这里的行为。

【问题讨论】:

【参考方案1】:

    在执行查询期间,如果网络缓冲区已满,SQL Server 可以暂停查询。如果客户端没有跟上读取网络的速度,就会发生这种情况,即。它不调用 SqlDataReader.Read()。 SQL Server 查询在网络缓冲区被释放时恢复,即。当客户端恢复 SqlDataReader.Read() 时。这意味着当您从数据读取器读取数据集结果时,查询仍在服务器上执行。还有更多细节,比如网络缓冲区的大小、客户端使用 SqlBytes.Stream 的 BLOB 操作等,但这个想法的要点是,慢速客户端会导致查询暂停,当客户端结束时查询结束.

    在正常隔离级别(已提交读)下读取数据时,SQL Server 将在其读取的行上放置短暂的共享锁。在更高的隔离级别下,锁的寿命很长,并一直保持到事务结束。

    如果不使用任何事务,则每个 SELECT 语句都会在语句执行期间创建一个隐式只读事务。

所以从1、2、3我们可以看出,在高隔离级别下运行查询的慢客户端会导致共享锁被持有很长时间。

现在你需要详细说明你在观察什么:

这个查询持有什么样的锁? S、U、X? 行、页、表? 范围锁定? 是否进行了锁升级? 为什么在查询期间持有锁? 您使用 REPEATABLE READ 还是 SERIALIZATION 隔离级别?如果是,为什么? 您是否使用锁定提示?如果是,为什么?

您最好的选择可能是snapshot isolation(至少需要 SQL Server 2005),作为快照隔离级别或作为读取提交的快照。这将完全消除锁定问题,但可能会对 tempdb 产生一些 IO 压力。

其他解决方案是使用游标,但会侵入现有代码库、复杂且仍然容易出错(必须正确设置游标类型)。

顺便说一句,我确实建议更改客户端行为。我假设您现在正在 SqlDataReader.Read 循环中在读取业务对象时将它们编组回来,这就是这样做的方法。预先读入内存然后编组可能会在大型数据集上增加更多问题。

【讨论】:

第 1 点很有趣:我经常看到它 很棒的答案,谢谢。该查询在已提交读隔离级别下执行,并且设置 READ COMMITTED SNAPSHOT ON 确实解决了阻塞问题。我仍然很好奇为什么锁(其中大部分是 S 页锁)在查询期间被持有,因为没有使用锁提示,并且根据 SQL Profiler,没有发生锁升级。跨度> 【参考方案2】:

选择临时表会减少锁定持续时间

select blah from tbl into #temp << locks held and released

select * from #temp << take all the time you want now

【讨论】:

以上是关于使用 SQL Server 锁定的 DataReader 行为的主要内容,如果未能解决你的问题,请参考以下文章

sql server使用不同的锁模式锁定资源,这些模式有哪三种

SQL Server中如何定位Row Lock锁定哪一行数据

SQL Server中如何定位Row Lock锁定哪一行数据

SQL Server锁定的DataReader行为

SQL Server中如何定位Row Lock锁定哪一行数据

SQL Server - 合并大表而不锁定数据