7

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

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吗?

4

1 回答 1

10

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

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

RCSI 中的语句会看到在语句开始之前提交的所有内容。事务中的每个新语句都会获取最近提交的更改。

请参阅此 MSDN 博客文章

设置脚本

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

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

痕迹

注意:如果您想知道为什么某些行级S锁似乎丢失了,这是此处解释的优化

于 2011-01-23T12:13:32.650 回答