首页 > 代码库 > T-SQL 重复读(Double Read)问题的理解

T-SQL 重复读(Double Read)问题的理解

我的理解是:

  step1,假设表里有100行有序记录, 事务1从row 1 开始读取到了row 50 并准备继续读取完这100行。

  要注意的是,sql server 会自动释放已经读取了的row的锁。

  step2,这时候,另外一个事务2 修改了 事务1已经读取并且被 sql server 释放掉锁的前面50行中的某些数据。

  这修改操作导致了数据排序发生变化,可能原来已经读取了的第20行现在排到了50行后面去。

  step3,然后,事务1继续读取剩下的数据。 就可能发现有数据被重复读取出来了。

 

demo如下:

  1. 建立表和填充数据。

CREATE TABLE dbo.testDoubleRead(id int identity(10,1) PRIMARY KEY,text_asc nvarchar(20))goCREATE NONCLUSTERED INDEX IX_testDoubleRead_text_asc ON dbo.testDoubleRead(text_asc ASC);INSERT INTO dbo.testDoubleRead (text_asc)VALUES(A),(B),(C),(D)

 

注意非聚集索引的排序是 ASC,这时候表里的数据是:

SELECT * FROM dbo.testDoubleRead/*id    text_asc10    A11    B12    C13    D*/

 

  2. 开一个新session,执行下面这段不完整的事务:

--SESSION 1 SCRIPTBEGIN TRANSACTIONUPDATE dbo.testDoubleReadSET text_asc = UPDATE_CWHERE id = 12

  3. 开另外一个session, 执行下面的查询:

SELECT * FROM dbo.testDoubleRead

  显然,这查询是会被session1 block住的。

  

  4. 回到session1, 执行完毕如下代码:

UPDATE dbo.testDoubleReadSET text_asc = UPDATE_B_2WHERE id = 11COMMIT TRANSACTION

 

  5. 这时候, session2 会查询完毕,结果如下:

SELECT * FROM dbo.testDoubleRead/*id    text_asc10    A11    B13    D11    UPDATE_B_212    UPDATE_C*/

  可以看到,有两条 id = 11的记录!

 

再次分析下:

  1。按照 非聚集索引的定义,刚开始的数据应该是这样的

/*id    text_asc10    A11    B12    C13    D*/

 

  2. session1 开启事务并修改了id=12 的行,但没有提交。 这时候,session2 尝试读取数据 但只能读取到 id=11的,不能再朝下读取了;除非session1 释放id=12 的行。

  3. 接上面,session1 把id=12 的行修改了,根据非聚集索引的排序规则 text_asc ASC,可以确定值‘UPDATE_C’是应该排到最后一行的。 而且id=11的新值‘UPDATE_B’是在倒数第二行。

  理想数据的样子应该是这样的:

/*id    text_asc10    A13    D11    UPDATE_B_212    UPDATE_C*/

  

  4.session修改完id=11以后就提交,这样session2就可以继续之前的查询了。注意的是,session2在被block之前已经读取了:  (id=11,text_asc=‘B‘)

  5.综合上面的几点,session2继续按照非聚集索引的顺序读取(id=13开始),而不会清空之前已经读取完毕的(id=11,text_asc=‘B‘)。

  所以最终的结果就是这样的了:

/*id    text_asc10    A11    B    --在被session1的修改事务block之前读取了这行13    D    --session1提交事务以后,session2从这行继续读11    UPDATE_B_212    UPDATE_C*/

 

 

 

  

 

T-SQL 重复读(Double Read)问题的理解