场景:
订票系统,多用户同时抢购某一趟列车的车票,同时操作数据库。
并发操作带来的数据不一致性包括
1)丢失修改(lost update)
2)不可重复读(non-repeatable read)
3)脏读(dirty read)
并发控制类型:
**1. 悲观并发控制:**用到锁来保护数据。用于锁消耗低于回滚事务的成本环境中;
**2. 乐观并发控制:**并发过程中不产生锁,读取数据后检查用户数据(如通过行版本号判断),判断是否产生错误, 回滚事务。用于数据争用少的环境。
锁与事务
**锁:**是数据库引擎用来同步多个用户同事对一个数据块访问的一种机制,通俗理解为占有权(独占、非独占)。
锁的粒度和层次结构
根据锁定的资源不同,锁的粒度也有很多种,比如常见的表锁、行锁。
锁的范围越小,对并发更友好,但是系统开销更大,因为锁定的范围越小,需要的锁就越多。
锁的实例
执行SQL语句SP_LOCK查询数据库的中锁
spid dbid ObjId IndId Type Resource Mode Status
56 8 0 0 DB S GRANT
61 8 0 0 DB S GRANT
65 8 0 0 DB S GRANT
65 1 1787153412 0 TAB IS GRANT
65 32767 -571204656 0 TAB Sch-S GRANT
79 8 0 0 DB S GRANT
spid:进程id
dbid:数据库id
objid:数据库内对象id
IndId: 持有锁的索引的标识号。
type:锁的资源类型
mode:锁的类型
资源类型说明:
RID = 表中单个行的锁,由行标识符 (RID) 标识。
KEY = 索引内保护可串行事务中一系列键的锁。
PAG = 数据页或索引页的锁。
EXT = 对某区的锁。
TAB = 整个表(包括所有数据和索引)的锁。
DB = 数据库的锁。
FIL = 数据库文件的锁。
APP = 指定的应用程序资源的锁。
MD = 元数据或目录信息的锁。
HBT = 堆或 B 树索引的锁。在 SQL Server 中此信息不完整。
AU = 分配单元的锁。在 SQL Server 中此信息不完整。
锁类型说明
Sch-S = 架构稳定性。确保在任何会话持有对架构元素(例如表或索引)的架构稳定性锁时,不删除该架构元素。
Sch-M = 架构修改。必须由要更改指定资源架构的任何会话持有。确保没有其他会话正在引用所指示的对象。
S = 共享。授予持有锁的会话对资源的共享访问权限。
U = 更新。指示对最终可能更新的资源获取的更新锁。用于防止一种常见的死锁,这种死锁在多个会话锁定资源以便稍后对资源进行更新时发生。
X = 排他。授予持有锁的会话对资源的独占访问权限。
IS = 意向共享。指示有意将 S 锁放置在锁层次结构中的某个从属资源上。
IU = 意向更新。指示有意将 U 锁放置在锁层次结构中的某个从属资源上。
IX = 意向排他。指示有意将 X 锁放置在锁层次结构中的某个从属资源上。
SIU = 共享意向更新。指示对有意在锁层次结构中的从属资源上获取更新锁的资源进行共享访问。
SIX = 共享意向排他。指示对有意在锁层次结构中的从属资源上获取排他锁的资源进行共享访问。
UIX = 更新意向排他。指示对有意在锁层次结构中的从属资源上获取排他锁的资源持有的更新锁。
BU = 大容量更新。用于大容量操作。
RangeS_S = 共享键范围和共享资源锁。指示可串行范围扫描。
RangeS_U = 共享键范围和更新资源锁。指示可串行更新扫描。
RangeI_N = 插入键范围和 Null 资源锁。用于在将新键插入索引前测试范围。
RangeI_S = 键范围转换锁。由 RangeI_N 和 S 锁的重叠创建。
RangeI_U = 由 RangeI_N 和 U 锁的重叠创建的键范围转换锁。
RangeI_X = 由 RangeI_N 和 X 锁的重叠创建的键范围转换锁。
RangeX_S = 由 RangeI_N 和 RangeS_S 锁的重叠创建的键范围转换锁。
RangeX_U = 由 RangeI_N 和 RangeS_U 锁的重叠创建的键范围转换锁。
RangeX_X = 排他键范围和排他资源锁。这是在更新范围中的键时使用的转换锁。
各种锁之间的兼容性
排他锁的隔离级别最高,例如对A启用了排他锁,那么别的会话事务将无法对A加任何锁。
事务隔离级别
当前会话事务隔离级别查询:DBCC USEROPTIONS
READ UNCOMMITTED:隔离事务的最低级别,不进行锁定,也不会对独占锁定特殊处理,只能保证不读取物理上损坏的数据。
READ COMMITED:数据库引擎的默认级别。要求对读取的每一行实行共享锁定。如果行没有被更新,将会释放锁定。
REPEATABLE READ:要求对读取的每一行实行共享锁定。并在整个事务执行过程中保持锁定状态。
SERIALIZABLE:隔离事务的最高级别。在整个事务执行过程中保持关键字范围的锁定,事务之间完全隔离。
默认隔离级别中更新跟新增数据开启排它锁,查询的瞬时开启共享锁(不会持续整个事务),若Update语句值有指定的主键那么开始的是行级锁,只会锁定需要更改的那一行,否则开启的就是表级锁。
示例:语句1通过主键ID更新数据,开启事务,等待10秒后提交事务,因为是行锁,所以不影响语句2根语句3的执行。
--语句1
BEGIN TRAN
UPDATE BankAccount
SET Account=1000
WHERE ID=1
waitfor delay '00:00:10'
COMMIT TRAN
--语句2
BEGIN TRAN
UPDATE BankAccount
SET Account=50001
WHERE ID=3
COMMIT TRAN
--语句3
SELECT * FROM BankAccount WHERE ID=2
--事务中自定义锁类型
BEGIN TRANSACTION
SELECT * FROM Person WITH (TABLOCK) --表共享锁
SELECT * FROM Person WITH (TABLOCKX) --表排它锁
SELECT * FROM Person WITH (NOLOCK) --不锁定 仅支持select语句
SELECT * FROM Person WITH (UPDLOCK) --更新所
SELECT * FROM Person WITH (READUNCOMMITTED) --读取数据可能含脏数据
SELECT * FROM Person WITH (READCOMMITTED) --仅读取提交后数据
COMMIT TRANSACTION