from:http://www.cnblogs.com/chenmh/p/3998614.htmlhtml
标签: SQL SEERVER/MSSQL SERVER/SQL/事务隔离级别选项/设置数据库事务级别数据库
SQL 事务隔离级别并发
概述post
隔离级别用于决定若是控制并发用户如何读写数据的操做,同时对性能也有必定的影响做用。性能
步骤测试
事务隔离级别经过影响读操做来间接地影响写操做;能够在回话级别上设置事务隔离级别也能够在查询(表级别)级别上设置事务隔离级别。
事务隔离级别总共有6个隔离级别:
READ UNCOMMITTED(未提交读,读脏),至关于(NOLOCK)
READ COMMITTED(已提交读,默认级别)
REPEATABLE READ(能够重复读),至关于(HOLDLOCK)
SERIALIZABLE(可序列化)
SNAPSHOT(快照)
READ COMMITTED SNAPSHOT(已经提交读隔离)
对于前四个隔离级别:READ UNCOMMITTED<READ COMMITTED<REPEATABLE READ<SERIALIZABLE
隔离级别越高,读操做的请求锁定就越严格,锁的持有时间久越长;因此隔离级别越高,一致性就越高,并发性就越低,同时性能也相对影响越大.spa
获取事务隔离级别(isolation level)版本控制
DBCC USEROPTIONS
设置隔离code
设置回话隔离 SET TRANSACTION ISOLATION LEVEL <ISOLATION NAME> --注意:在设置回话隔离时(REPEATABLE READ)两个单词须要用空格间隔开,可是在表隔离中能够粘在一块儿(REPEATABLEREAD) 设置查询表隔离 SELECT ....FROM <TABLE> WITH (<ISOLATION NAME>)
1.READ UNCOMMITTEDhtm
READ UNCOMMITTED:未提交读,读脏数据
默认的读操做:须要请求共享锁,容许其余事物读锁定的数据但不容许修改.
READ UNCOMMITTED:读操做不申请锁,运行读取未提交的修改,也就是容许读脏数据,读操做不会影响写操做请求排他锁.
建立测试数据
IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL, Price FLOAT NOT NULL ); INSERT INTO Orders VALUES(10,10.00),(11,11.00),(12,12.00),(13,13.00),(14,14.00); GO SELECT ID,Price FROM Orders
新建回话1将订单10的价格加1
BEGIN TRANSACTION UPDATE Orders SET Price=Price+1 WHERE ID=10 SELECT ID,Price FROM Orders WHERE ID=10
在另外一个回话2中执行查询操做
首先不添加隔离级别,默认是READ COMMITTED,因为数据以前的更新操做使用了排他锁,因此查询一直在等待锁释放*/ SELECT ID,Price FROM Orders WHERE ID=10 ---将查询的隔离级别设置为READ UNCOMMITTED容许未提交读,读操做以前不请求共享锁。 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED SELECT ID,Price FROM Orders WHERE ID=10; --固然也可使用表隔离,效果是同样的 SELECT ID,Price FROM Orders WITH (NOLOCK) WHERE ID=10
假设在回话1中对操做执行回滚操做,这样价格仍是以前的10,可是回话2中则读取到的是回滚前的价格11,这样就属于一个读脏操做
ROLLBACK TRANSACTION
2.READ COMMITTED
READ COMMITTED(已提交读)是SQL SERVER默认的隔离级别,能够避免读取未提交的数据,隔离级别比READ UNCOMMITTED未提交读的级别更高;
该隔离级别读操做以前首先申请并得到共享锁,容许其余读操做读取该锁定的数据,可是写操做必须等待锁释放,通常读操做读取完就会马上释放共享锁。
新建回话1将订单10的价格加1,此时回话1的排他锁锁住了订单10的值
BEGIN TRANSACTION UPDATE Orders SET Price=Price+1 WHERE ID=10 SELECT ID,Price FROM Orders WHERE ID=10
在回话2中执行查询,将隔离级别设置为READ COMMITTED
SET TRANSACTION ISOLATION LEVEL READ COMMITTED SELECT ID,Price FROM Orders WHERE ID=10 ---因为READ COMMITTED须要申请得到共享锁,而锁与回话1的排他锁冲突,回话被堵塞, ----在回话1中执行事务提交 COMMIT TRANSACTION /*因为回话1事务提交,释放了订单10的排他锁,此时回话2申请共享锁成功查到到订单10的价格为修改后的价格11,READ COMMITTED因为是已提交读隔离级别,因此不会读脏数据. 可是因为READ COMMITTED读操做一完成就当即释放共享锁,读操做不会在一个事务过程当中保持共享锁,也就是说在一个事务的的两个查询过程之间有另外一个回话对数据资源进行了更改,会致使一个事务的两次查询获得的结果不一致,这种现象称之为不可重复读.*/
重置数据
UPDATE Orders SET Price=10 WHERE ID=10
3.REPEATABLE READ
REPEATABLE READ(可重复读):保证在一个事务中的两个读操做之间,其余的事务不能修改当前事务读取的数据,该级别事务获取数据前必须先得到共享锁同时得到的共享锁不当即释放一直保持共享锁至事务完成,因此此隔离级别查询完并提交事务很重要。
在回话1中执行查询订单10,将回话级别设置为REPEATABLE READ
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION SELECT ID,Price FROM Orders WHERE ID=10
新建回话2修改订单10的价格
UPDATE Orders SET Price=Price+1 WHERE ID=10 ---因为回话1的隔离级别REPEATABLE READ申请的共享锁一直要保持到事务结束,因此回话2没法获取排他锁,处于等待状态
在回话1中执行下面语句,而后提交事务
SELECT ID,Price FROM Orders WHERE ID=10 COMMIT TRANSACTION
回话1的两次查询获得的结果一致,前面的两个隔离级别没法获得一致的数据,此时事务已提交同时释放共享锁,回话2申请排他锁成功,对行执行更新
REPEATABLE READ隔离级别保证一个事务中的两次查询到的结果一致,同时保证了丢失更新
丢失更新:两个事务同时读取了同一个值而后基于最初的值进行计算,接着再更新,就会致使两个事务的更新相互覆盖。
例如酒店订房例子,两我的同时预约同一酒店的房间,首先两我的同时查询到还有一间房间能够预约,而后两我的同时提交预约操做,事务1执行number=1-0,同时事务2也执行number=1-0最后修改number=0,这就致使两我的其中一我的的操做被另外一我的所覆盖,REPEATABLE READ隔离级别就能避免这种丢失更新的现象,当事务1查询房间时事务就一直保持共享锁直到事务提交,而不是像前面的几个隔离级别查询完就是否共享锁,就能避免其余事务获取排他锁。
4.SERIALIZABLE
SERIALIZABLE(可序列化),对于前面的REPEATABLE READ能保证事务可重复读,可是事务只锁定查询第一次运行时获取的数据资源(数据行),而不能锁定查询结果以外的行,就是本来不存在于数据表中的数据。所以在一个事务中当第一个查询和第二个查询过程之间,有其余事务执行插入操做且插入数据知足第一次查询读取过滤的条件时,那么在第二次查询的结果中就会存在这些新插入的数据,使两次查询结果不一致,这种读操做称之为幻读。
为了不幻读须要将隔离级别设置为SERIALIZABLE
IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL PRIMARY KEY, Price FLOAT NOT NULL, type INT NOT NULL ); INSERT INTO Orders VALUES(10,10.00,1),(11,11.00,1),(12,12.00,1),(13,13.00,1),(14,14.00,1); GO
在回话1中执行查询操做,并将事务隔离级别设置为REPEATABLE READ(先测试一下前面更低级别的隔离)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE TYPE=1
在回话2中执行插入操做
INSERT INTO Orders VALUES(15,15.00,1)
返回回话1从新执行查询操做并提交事务
SELECT ID,Price,type FROM Orders WHERE TYPE=1 COMMIT TRANSACTION
结果回话1中第二次查询到的数据包含了回话2新插入的数据,两次查询结果不一致(验证以前的隔离级别不能保证幻读)
从新插入测试数据
IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL PRIMARY KEY, Price FLOAT NOT NULL, type INT NOT NULL ); INSERT INTO Orders VALUES(10,10.00,1),(11,11.00,1),(12,12.00,1),(13,13.00,1),(14,14.00,1); GO
接下来将回话级别设置为SERIALIZABLE,在回话1中执行查询操做,并将事务隔离级别设置为SERIALIZABLE
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE TYPE=1
在回话2中执行插入操做
INSERT INTO Orders VALUES(15,15.00,1)
返回回话1从新执行查询操做并提交事务
SELECT ID,Price,type FROM Orders WHERE TYPE=1 COMMIT TRANSACTION
两次执行的查询结果相同
重置全部打开回话的默认隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
5.SNAPSHOT
SNAPSHOT快照:SNAPSHOT和READ COMMITTED SNAPSHOT两种隔离(能够把事务已经提交的行的上一版本保存在TEMPDB数据库中)
SNAPSHOT隔离级别在逻辑上与SERIALIZABLE相似
READ COMMITTED SNAPSHOT隔离级别在逻辑上与 READ COMMITTED相似
不过在快照隔离级别下读操做不须要申请得到共享锁,因此即使是数据已经存在排他锁也不影响读操做。并且仍然能够获得和SERIALIZABLE与READ COMMITTED隔离级别相似的一致性;若是目前版本与预期的版本不一致,读操做能够从TEMPDB中获取预期的版本。
若是启用任何一种基于快照的隔离级别,DELETE和UPDATE语句在作出修改前都会把行的当前版本复制到TEMPDB中,而INSERT语句不须要在TEMPDB中进行版本控制,由于此时尚未行的旧数据
不管启用哪一种基于快照的隔离级别都会对更新和删除操做产生性能的负面影响,可是有利于提升读操做的性能由于读操做不须要获取共享锁;
5.1SNAPSHOT
SNAPSHOT 在SNAPSHOT隔离级别下,当读取数据时能够保证操做读取的行是事务开始时可用的最后提交版本
同时SNAPSHOT隔离级别也知足前面的已提交读,可重复读,不幻读;该隔离级别实用的不是共享锁,而是行版本控制
使用SNAPSHOT隔离级别首先须要在数据库级别上设置相关选项
在打开的全部查询窗口中执行如下操做
ALTER DATABASE TEST SET ALLOW_SNAPSHOT_ISOLATION ON;
重置测试数据
IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL PRIMARY KEY, Price FLOAT NOT NULL, type INT NOT NULL ); INSERT INTO Orders VALUES(10,10.00,1),(11,11.00,1),(12,12.00,1),(13,13.00,1),(14,14.00,1); GO
在回话1中打开事务,将订单10的价格加1,并查询跟新后的价格 BEGIN TRANSACTION UPDATE Orders SET Price=Price+1 WHERE ID=10 SELECT ID,Price,type FROM Orders WHERE ID=10 ---查询到更新后的价格为11 ---在回话2中将隔离级别设置为SNAPSHOT,并打开事务(此时查询也不会由于回话1的排他锁而等待,依然能够查询到数据) SET TRANSACTION ISOLATION LEVEL SNAPSHOT BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE ID=10 ---查询到的结果仍是回话1修改前的价格,因为回话1在默认的READ COMMITTED隔离级别下运行,SQL SERVER必须在更新前把行的一个副本复制到TEMPDB数据库中 --在SNAPSHOT级别启动事务会请求行版本 ---如今在回话1中执行提交事务,此时订单10的价格为11 COMMIT TRANSACTION ---再次在回话二中查询订单10的价格并提交事务,结果仍是10,由于事务要保证两次查询的结果相同 SELECT ID,Price,type FROM Orders WHERE ID=10 COMMIT TRANSACTION ---此时若是在回话2中从新打开一个事务,查询到的订单10的价格则是11 BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE ID=10 COMMIT TRANSACTION /*SNAPSHOT隔离级别保证操做读取的行是事务开始时可用的最后已提交版本,因为回话1的事务未提交,因此订单10的最后提交版本仍是修改前的价格10,因此回话2读取到的价格是回话2事务开始前的已提交版本价格10,当回话1提交事务后,回话2从新新建一个事务此时事务开启前的价格已是11了,因此查询到的价格是11,同时SNAPSHOT隔离级别还能保证SERIALIZABLE的隔离级别*/
5.2READ COMMITTED SNAPSHOT
READ COMMITTED SNAPSHOT也是基于行版本控制,可是READ COMMITTED SNAPSHOT的隔离级别是读操做以前的最后已提交版本,而不是事务前的已提交版本,有点相似前面的READ COMMITTED能保证已提交读,可是不能保证可重复读,不能避免幻读,可是又比 READ COMMITTED隔离级别多出了不须要获取共享锁就能够读取数据
要启用READ COMMITTED SNAPSHOT隔离级别一样须要修改数据库选项,在回话1,回话2中执行如下操做(执行下面的操做当前链接必须是数据库的惟一链接,能够经过查询已链接当前数据库的进程,而后KILL掉那些进程,而后再执行该操做,不然可能没法执行成功)
ALTER DATABASE TEST SET READ_COMMITTED_SNAPSHOT ON IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL PRIMARY KEY, Price FLOAT NOT NULL, type INT NOT NULL ); INSERT INTO Orders VALUES(10,10.00,1),(11,11.00,1),(12,12.00,1),(13,13.00,1),(14,14.00,1); GO -----在回话1中打开事务,将订单10的价格加1,并查询跟新后的价格,并保持事务一直处于打开状态 BEGIN TRANSACTION UPDATE Orders SET Price=Price+1 WHERE ID=10 --查询到的价格是11 SELECT ID,Price,type FROM Orders WHERE ID=10 ---在回话2中打开事务查询订单10并一直保持事务处于打开状态(此时因为回话1还未提交事务,因此回话2中查询到的仍是回话1执行事务以前保存的行版本) BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE ID=10 --查询到的价格仍是10 ---在回话1中提交事务 COMMIT TRANSACTION ---在回话2中再次执行查询订单10的价格,并提交事务 SELECT ID,Price,type FROM Orders WHERE ID=10 COMMIT TRANSACTION --此时的价格为回话1修改后的价格11,而不是事务以前已提交版本的价格,也就是READ COMMITTED SNAPSHOT隔离级别在同一事务中两次查询的结果不一致.
关闭全部链接,而后打开一个新的链接,禁用以前设置的数据库快照隔离级别选项
ALTER DATABASE TEST SET ALLOW_SNAPSHOT_ISOLATION OFF; ALTER DATABASE TEST SET READ_COMMITTED_SNAPSHOT OFF;
总结
理解了事务隔离级别有助于理解事务的死锁。
若是以为文章对你们对事务隔离级别的理解有所帮助,麻烦给个推荐,谢谢!!!