SQL Server事务的隔离级别
作者:网络转载 发布时间:[ 2016/9/13 10:19:44 ] 推荐标签:数据库 SQL Server
传统隔离级别
隔离级别确定了并发用户读取或者写入的行为。读取者可以是任何选择数据的语句,默认情况下使用共享锁。写入者是任何对表进行修改的语句,并且需要一个排它锁。
SQL Server支持4个基于悲观并发控制(锁定)的传统隔离级别:READ UNCOMMITTED,READ COMMITTED, REPEATABLE READ与SERIALIZABLE。对于这4个隔离级别,隔离级别越高,读取者请求的锁越强,并且持续时间越强。因此随着隔离级别的提高,一致性越高而并发性越低。
READ UNCOMMITTED
是可用的低隔离级别。在该隔离级别中,读取者不需要请求共享锁。不要求共享锁的读者不会与持有排它锁的写入者发生冲突,这意味着读取者可以读取未提交的更改(脏读)。同时也意味着读取者不会干扰排它锁的写入者,那么读取者在该隔离方式下读取数据时,写入者可以更改数据。
建立如下的连接1,并保持事务处于打开状态:
BEGIN TRAN;
UPDATE Production.Products SET unitprice += 1.00 WHERE productid = 2;
SELECT productid, unitprice FROM Production.Products WHERE productid = 2;
该事务拥有产品id为2的那个产品行的排他锁;终显示:
productid unitprice
---------- ----------
2 20.00
那么执行连接2,设置隔离级别为READ UNCOMMITTED:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT productid, unitprice FROM Production.Products WHERE productid = 2;
显示结果如下:
productid unitprice
---------- ----------
2 20.00
尽管连接1中的事务还没有提交,但是连接2却显示已经更改但未提交的数据。
READ COMMITTED
这是默认的隔离级别,该隔离级别仅允许读取者读取已经提交的更改,那么读取者必须要获得一个共享锁来防止未提交的更改。当写入者拥有一个排它锁时,读取者的共享锁将与之冲突,此时必须等待事务结束后才能获得共享锁,从而读取数据。
如果修改上面的连接2:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT productid, unitprice FROM Production.Products WHERE productid = 2;
那么当连接1的事务没有提交时,该查询便会阻塞。但是读取者所获得共享锁的持续时间仅在与SELECT语句读取间,而不是在整个事务中都拥有共享锁。
REPEATABLE READ
如果希望确保在同一事务中的多次读取之间没有其他事务能够修改值,那么要使用REPEATABLE READ隔离级别。此时,读取者不仅需要一个共享锁才能够读写,而且直到事务结束都持有锁。那么其它事务中任何尝试获得排它锁的写操作将在事务未提交之前被阻塞。这样确保了数据在整个事务中的一致性。
在连接1中设置REPEATABLE READ隔离级别:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRAN;
SELECT productid, unitprice FROM Production.Products WHERE productid = 2;
输出:
productid unitprice
---------- ----------
2 19.00
连接2如下:
UPDATE Production.Products SET unitprice += 1.00 WHERE productid = 2;
那么由于连接1事务未提交,连接2请求的排它锁与共享锁冲突,连接2中的update语句被阻塞。
REPEATABLE READ会造成丢失更新,比如两个事务同时读取一个值并尝试更新该值。由于在读取后双方都会保持它们的共享锁,那么对于两个事务中的任何一个更新都不会成功。从而造成死锁。
SERIALIZABLE
SERIALIZABLE这种隔离级别可以防止“幻读”。考虑这样的情况,事务锁定的行是在查询第一次运行时确定的,假如在事务进行第二次相同刷选条件下的查询时,其它事务添加了新行,而且新行位于查询筛选范围中,那么前后两次查询结果不一致。从而产生“幻读”。将隔离级别设置为SERIALIZABLE级别,那么读取者锁定的行将包括查询筛选所限定的整个范围。这意味着读者锁定的不仅是查询筛选所限定的现有行,也包括将来的行。这样,其他事务尝试添加读取者查询筛选所限定的行将被阻塞。
连接1如下:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN;
SELECT productid, productname, categoryid, unitprice
FROM Production.Products WHERE categoryid = 1;
结果如下:
productid productname categoryid unitprice
---------- --------------- ---------- ----------
1 Product HHYDP 1 18.00
2 Product RECZE 1 19.00
...
76 Product JYGFE 1 18.00
连接2如下:
INSERT INTO Production.Products(productname, supplierid, categoryid, unitprice, discontinued)
VALUES('Product ABCDE', 1, 1, 20.00, 0);
连接2的插入将被阻止。
基于行版本的隔离级别
SQL Server还支持两种基于乐观并发控制(行版本控制)的隔离级别:SNAPSHOT和READ COMMITTED SNAPSHOT。
这种隔离级别被称于基于行版本或者快照的隔离级别,SQL Server 能够在tempdb中存储之前提交的行版本。读取者不请求共享锁。
SNAPSHOT
在SNAPSHOT隔离级别下,读取者在读取数据时,它将确保获得事务启动时近提交的可用行版本。这意味着,保证获得的是提交后的读取并且可以重复读取,以及确保获得的不是幻读。要允许事务可以以SNAPSHOT隔离级别工作,首先要在数据库级别允许此选项:
ALTER DATABASE dbname SET ALLOW_SNAPSHOT_ISOLATION ON;
在连接1中,我们运行READ COMMITTED隔离级别,更新产品2的价格,从19.00变为20.00:
BEGIN TRAN;
UPDATE Production.Products SET unitprice += 1.00 WHERE productid = 2;
SELECT productid, unitprice FROM Production.Products WHERE productid = 2;
结果显示:
productid unitprice
---------- ----------
2 20.00
连接2设置SNAPSHOT隔离级别:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRAN;
SELECT productid, unitprice FROM Production.Products WHERE productid = 2;
结果显示事务运行时可用的上次提交的行版本:
productid unitprice
---------- ----------
2 19.00
回到连接1,提交事务:
COMMIT TRAN;
提交事务后,价格为20.00的当期版本变成了新的提交版本。
在连接2中再次读取数据,并提交,此时显示的仍是19.00,因为事务还未提交,事务所保存的快照并不会发生改变:
productid unitprice
---------- ----------
2 19.00
在连接2中我们重新打开一个事务:
BEGIN TRAN;
SELECT productid, unitprice FROM Production.Products WHERE productid = 2;
COMMIT TRAN;
此时事务运行时的数据快照为20.00,故显示:
productid unitprice
---------- ----------
2 20.00
对于旧的快照版本,如果没有事务使用它们,那么清理线程会及时清除它们。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11