一、锁
锁是一种安全机制,控制并发操作,防止用户读取其他用户正在更改的数据,或者多用户同时修改一个数据,从而保证事物的完整性和数据库的一致性。SQLserver 会自动强制执行锁,但是用户可以通过对锁进行了解并在应用程序中自定义锁来设计出高效率的应用程序。锁确定了并发事物访问资源的方式。
二、锁分类
共享锁:
共享锁,锁定的资源可以被其他用户读取,但是其他用户不能听修改他(只读操作), 例如在select语句执行时,sqlserver 会对 对象进行共享锁锁定,对加共享锁的资源读取完毕之后,共享锁立即释放。(对于事物级别中 repeattable read,是这样的,在一个事物中,select 语句获取共享锁,查询完之后,不会释放共享锁,只有等到事物结束才会释放共享锁。)
排他锁:
排它锁只允许锁定他的程序操作他。其他任何操作都不会被接受。例如执行update insert delete时sqlserver会自动是用排他锁,确保不会同时对一个资源多种操作。当对象上有其他锁时,无法对资源添加排它锁(这个也是事物中read commited 原理)。排它锁一直到事物结束才释放。
更新锁:
用户更新资源时,防止死锁。
下面是死锁现象
上面两个相同的事物同时执行,同时获取共享锁, 当他们更新的时候需要获取排它锁,但是由于其他事物存在其他类型的锁,不能使用排它锁,导致锁等待,都在等待另外一个事物释放共享锁。导致锁死。
更新锁,可以防止死锁问题。因为一次只能有一个事物获取资源的更新锁,其他事物只能获得共享锁。sqlserver准备更新数据时,首先对资源添加更新锁,这样其他事物不能修改,只能读取。等到sqlserver确定要更新资源时,自动将更新锁转换为排它锁,否则锁转化为共享锁。
三、事物级别
CREATE DATABASE Test GO USE [Test] GO /****** Object: Table [dbo].[UserTable] Script Date: 2017/7/20 14:00:15 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[UserTable]( [UserID] [int] IDENTITY(1,1) NOT NULL, [UserName] [nvarchar](50) NULL, [NewClo] [int] NULL, CONSTRAINT [PK_UserTable] PRIMARY KEY CLUSTERED ( [UserID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
数据库初始化脚本
四种常用级别,由低到高。设置事物级别 SET TRANSACTION ISOLATION LEVEL + 事物级别。默认事物级别为READ COMMIT
READ UNCOMMITED(未提交 读)
顾名思义就是读出了还没有提交的数据,特点:效率高,但是就是容易出现脏读。最低的事物隔离级别,仅保证不读取物理受损的数据。
--开始事物 BEGIN TRANSACTION --查询第一次,获取共享锁,查询完之后马上释放共享锁 SELECT * FROM UserTable AS ut --插入数据,获取 修改数据的 排它锁 insert INTO UserTable ( -- UserID -- this column value is auto-generated UserName, NewClo )VALUES(‘西伯利亚的狼‘,1) --等到15秒,让其他用户查询 WAITFOR DELAY ‘00:00:15‘ --第二次查询,会查到西伯利亚的狼 SELECT * FROM UserTable AS ut --commit時候自动释放排他锁 COMMIT TRANSACTION
新增脚本:添加数据事物
--设置事物级别 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED SELECT * FROM UserTable AS ut WHERE ut.UserName=‘西伯利亚的狼‘
查询脚本:设置事物级别并查询上面添加进去的数据
--即使上面有拍它所,也可以查出来 SELECT * FROM UserTable AS ut WITH (NOLOCK) WHERE ut.UserName=‘西伯利亚的狼‘
设置锁查询:表明 as 别名 with(nolock),使锁定的数据也能读出来。
先执行新增脚本,在马上执行查询脚本,由于新增脚本要等待15秒,事物未提交,但是查询脚本还是把未提交事物的数据查询出来了,设置锁查询with (nolock)也可以查询出来。称为脏读。(但是这种读取的效率高,不等待其他锁,就是会出现脏读。很多和钱打交道的行业比较敏感)
READ COMMIT(提交 读)
顾名思义就是提交的数据才能读出来。
--开始事物 BEGIN TRANSACTION --查询第一次,获取共享锁,查询完之后马上释放共享锁 SELECT * FROM UserTable AS ut --插入数据,获取 修改数据的 排它锁 insert INTO UserTable ( -- UserID -- this column value is auto-generated UserName, NewClo )VALUES(‘西伯利亚的狼‘,1) --等到15秒,让其他用户查询 WAITFOR DELAY ‘00:00:15‘ --第二次查询,会查到西伯利亚的狼 SELECT * FROM UserTable AS ut --commit時候自动释放排他锁 COMMIT TRANSACTION
新增脚本:添加数据事物
--设置事物级别 SET TRANSACTION ISOLATION LEVEL READ COMMITTED SELECT * FROM UserTable AS ut WHERE ut.UserName=‘西伯利亚的狼‘
查询脚本:设置事物级别并查询上面添加进去的数据
先执行新增脚本,在马上执行查询脚本,由于新增脚本要等待15秒,事物未提交,查询脚本不能把新加的数据读出来,只能等事物提交完毕,才能读来。默认事物级别
REPEATTABLE READ(重复 读)
select语句读取出来的数据在整个语句执行过程中不会被更改。同一个事物中,两个相同的SQL语句独处内容不同,出现幻读。目的保持同一个事物中相同的SQL语句读出的内容相同。
--设置锁级别 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION SELECT * FROM Test AS t WHERE t.ID=1 --登台15秒 让其他用户修改这一条数据 WAITFOR DELAY ‘00:00:15‘ SELECT * FROM Test AS t WHERE t.ID=1 COMMIT TRANSACTION
查询脚本:设置了重复读级别,查询的共享锁只有在事物结束时才被释放,导致其他修改操作不能获取排它锁。
UPDATE Test SET -- ID -- this column value is auto-generated Name = NEWID() WHERE ID=1
修改脚本:修改正在被查询的数据,由于不能获取到排它锁,所以等待
先执行查询脚本,在马上执行修改脚本,查询脚本等待15秒,共享锁没有释放,所以导致修改脚本也在等待。
SERIALIZABLE 可串行读
最高的事物隔离级别,使事物之间完全隔离。将共享锁保持到事物完成。事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。