一. 为什么要引入锁
多个用户同时对数据库的并发操作时会带来以下数据不一致的问题: 丢失更新
A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统 脏读
A用户修改了数据,随后B用户又读出该数据,但A用户因为某些原因取消了对数据的修改,数据恢复原值,此时B得到的数据就与数据库内的数据产生了不一致 不可重复读
A用户读取数据,随后B用户读出该数据并修改,此时A用户再读取数据时发现前后两次的值不一致
并发控制的主要方法是封锁,锁就是在一段时间内禁止用户做某些操作以避免产生数据不一致
二 锁的隔离级别
ISO 标准定义了下列隔离级别,SQL Server 数据库引擎支持所有这些隔离级别:
隔离级别 | 定义 |
---|---|
未提交的读取 | 隔离事务的最低级别,只能保证不读取物理上损坏的数据。 在此级别上,允许脏读,因此一个事务可能看见其他事务所做的尚未提交的更改。 |
已提交的读取 | 允许事务读取另一个事务以前读取(未修改)的数据,而不必等待第一个事务完成。 SQL Server 数据库引擎保留写锁(在所选数据上获取)直到事务结束,但是一执行 SELECT 操作就释放读锁。 这是SQL Server 数据库引擎默认级别。 |
可重复的读取 | SQL Server 数据库引擎保留在所选数据上获取的读锁和写锁,直到事务结束。 但是,因为不管理范围锁,可能发生虚拟读取。 |
可序列化 | 隔离事务的最高级别,事务之间完全隔离。 SQL Server 数据库引擎保留在所选数据上获取的读锁和写锁,在事务结束时释放它们。SELECT 操作使用分范围的 WHERE 子句时获取范围锁,主要为了避免虚拟读取。注意: 请求可序列化隔离级别时,复制的表上的 DDL 操作和事务可能失败。 这是因为复制查询使用的提示可能与可序列化隔离级别不兼容。 |
SET TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SNAPSHOT | SERIALIZABLE }
三 锁的分类
1. 从数据库系统的角度来看,分为以下锁模式。
SQL Server 数据库引擎使用不同的锁模式锁定资源,这些锁模式确定了并发事务访问资源的方式。
下表显示了SQL Server 数据库引擎使用的资源锁模式。
锁模式 | 说明 |
---|---|
共享 (S) | 用于不更改或不更新数据的读取操作,如 SELECT 语句。 |
更新 (U) | 用于可更新的资源中。 防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。 |
排他 (X) | 用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。 确保不会同时对同一资源进行多重更新。 |
意向 | 用于建立锁的层次结构。 意向锁包含三种类型:意向共享 (IS)、意向排他 (IX) 和意向排他共享 (SIX)。 |
架构 | 在执行依赖于表架构的操作时使用。 架构锁包含两种类型:架构修改 (Sch-M) 和架构稳定性 (Sch-S)。 |
大容量更新 (BU) | 在将数据大容量复制到表中且指定了 TABLOCK 提示时使用。 |
键范围 | 当使用可序列化事务隔离级别时保护查询读取的行的范围。 确保再次运行查询时其他事务无法插入符合可序列化事务的查询的行。 |
2. 从程序员的角度看:分为乐观锁和悲观锁。
乐观锁:完全依靠数据库来管理锁的工作。
悲观锁:程序员自己管理数据或对象上的锁处理。
MS-SQLSERVER 使用锁在多个同时在数据库内执行修改的用户间实现悲观并发控制
四 锁的粒度 锁粒度是被封锁目标的大小,封锁粒度小则并发性高,但开销大,封锁粒度大则并发性低但开销小 SQL Server支持的锁粒度可以分为为行、页、键、键范围、索引、表或数据库获取锁 资源 描述 RID 行标识符。用于单独锁定表中的一行。 键 索引中的行锁。用于保护可串行事务中的键范围。 页 8 千字节 (KB) 的数据页或索引页。 扩展盘区 相邻的八个数据页或索引页构成的一组。 表 包括所有数据和索引在内的整个表。 DB 数据库。
五【干货】常见锁的问题
1 如何锁一个表的某一行
//设置事务隔离级别,默认是READ UNCOMMITTED SET TRANSACTION ISOLATION LEVEL SERIALIZABLEBEGIN TRANSELECT * FROM table ROWLOCK WHERE id = 1 --rollback tran
* 事务未提交,该行处于锁死状态。当其它会话尝试更新id为1的行记录时,该会话处于等待状态2 锁定数据库的一个表
BEGIN TRANSELECT * FROM table WITH (HOLDLOCK)--rollback tran
* 事务未提交,整个表处于锁死状态。当其它会话尝试更新表中记录时,该会话处于等待状态
加锁语句: MSSQL:
BEGIN TRANselect col1 from 表 (tablockx) where 1=0 ;
oracle:
LOCK TABLE 表 IN EXCLUSIVE MODE ;
加锁后其它人不可操作,直到加锁用户解锁,用commit或rollback解锁 几个例子帮助大家加深印象 设table1(A,B,C) A B C a1 b1 c1 a2 b2 c2 a3 b3 c3 1)排它锁 新建两个连接 在第一个连接中执行以下语句
begin tran update table1 set A='aa' where B='b2' waitfor delay '00:00:30' --等待30秒 commit tran
在第二个连接中执行以下语句
begin tran select * from table1 where B='b2' commit tran
若同时执行上述两个语句,则select查询必须等待update执行完毕才能执行即要等待30秒 2)共享锁 在第一个连接中执行以下语句
begin tran select * from table1 holdlock where B='b2' --holdlock人为加锁waitfor delay '00:00:30' --等待30秒 commit tran
在第二个连接中执行以下语句
begin tran select A,C from table1 where B='b2' update table1 set A='aa' where B='b2' commit tran
若同时执行上述两个语句,则第二个连接中的select查询可以执行 而update必须等待第一个事务释放共享锁转为排它锁后才能执行 即要等待30秒 3)死锁 增设table2(D,E) D E d1 e1 d2 e2 在第一个连接中执行以下语句
begin tran update table1 set A='aa' where B='b2' waitfor delay '00:00:30' update table2 set D='d5' where E='e1' commit tran
在第二个连接中执行以下语句
begin tran update table2 set D='d5' where E='e1' waitfor delay '00:00:10' update table1 set A='aa' where B='b2' commit tran
同时执行,系统会检测出死锁,并中止进程
END![da8a5121341f6596e402f7596c17267f.png](https://i-blog.csdnimg.cn/blog_migrate/51e00287623cb1fa75aedc47a3fab4bb.png)