对于单个语句,在SQL Server中读取已提交的隔离级别

时间:2011-01-23 05:27:16

标签: sql-server transactions read-committed-snapshot

说,我有一个人表,它只有一行 -

id = 1, name = 'foo'

在一个连接上

select p1.id, p1.name, p2.name
from person p1 
join person p2 on p1.id = p2.id

同时在另一个连接上:

update person set name = 'bar' where person.id = 1

Q1:是否有可能,我的选择会根据更新声明的时间返回这样的结果:

id = 1, p1.name = 'foo', p2.name = 'bar'

两个连接都不使用显式事务,并且都使用默认事务隔离级别READ COMMITTED。

问题实际上是帮助我理解,在语句完成之前,sql语句开头获取的锁是否继续存在,或者语句是否有可能释放锁并重新获取锁如果在同一语句中使用两次,则为同一行?

Q2:如果在数据库中设置了set read_committed_snapshot on,问题的答案会改变吗?

1 个答案:

答案 0 :(得分:9)

Q1:是的,这至少在理论上是完全可能的。 read committed只是保证你不会读取脏数据,它不会对一致性做出承诺。在读取提交级别时,只要读取数据就会释放共享锁(不是在事务结束时甚至在语句结束时)

Q2:是的,如果read_committed_snapshot开启,这个问题的答案就会改变。然后,您将获得语句级别一致性。我发现很难找到一个明确说明这一点的在线资源,但引用了“Microsoft SQL Server 2008 Internals”的p.648

  

RCSI的声明可以看到一切   在开始之前承诺   声明。每个新的声明在   交易回升最近   承诺的改变。

另外see this MSDN blog post

设置脚本

CREATE TABLE person 
(
id int primary key,
name varchar(50)
)

INSERT INTO person
values(1, 'foo');

连接1

while 1=1
update person SET name = CASE WHEN name='foo' then 'bar' ELSE 'foo' END

连接2

DECLARE @Results TABLE (
  id    int primary key,
  name1 varchar(50),
  name2 varchar(50))

while( NOT EXISTS(SELECT *
                  FROM   @Results) )
  BEGIN
      INSERT INTO @Results
      Select p1.id,
             p1.name,
             p2.name
      from   person p1
             INNER HASH join person p2
               on p1.id = p2.id
      WHERE  p1.name <> p2.name
  END

SELECT *
FROM   @Results  

结果

id          name1 name2
----------- ----- -----
1           bar   foo

查看Profiler中的其他联接类型,似乎无法在此特定查询的merge联接或nested loops计划下出现此问题(在获取所有内容之前不会释放锁定)但是问题仍然是read committed只是保证你不会读取脏数据,它不会对一致性做出承诺。在实践中,您可能不会因为您发布的确切查询而遇到此问题,因为SQL Server默认情况下不会选择此连接类型。然而,您只需依靠实现细节来产生您想要的行为。

Trace

注意:如果您想知道为什么某些行级S锁似乎缺失,那么这是an optimisation explained here