在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。为保证数据的一致性,需要对 并发操作进行控制
,因此产生了 锁
。同时 锁机制 也为实现MySQL的各个隔离级别提供了保证。 锁冲突 也是影响数据库 并发访问性能 的一个重要因素。所以锁对数据库而言显得尤其重要,也更加复杂。
并发事务访问相同记录的情况大致可以划分为3种
读-读
情况,即并发事务相继 读取相同的记录
。读取操作本身不会对记录有任何影响,并不会引起什么问题,所以允许这种情况的发生
有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失
,第二类更新丢失
有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读
,幻读
,不可重复读
怎么解决 脏读 、 不可重复读 、 幻读 这些问题呢?其实有两种可选的解决方案
多版本并发控制( MVCC )
,写操作进行 加锁
采用 MVCC 方式的话, 读-写 操作彼此并不冲突, 性能更高
。采用 加锁 方式的话, 读-写 操作彼此需要 排队执行 ,影响性能。
共享锁
、英文用 S
表示。针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻塞的。排他锁
、英文用 X
表示。当前写操作没有完成前,它会阻断其他写锁和读锁。这样就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源。需要注意的是对于 InnoDB 引擎来说,读锁和写锁可以加在表上,也可以加在行上
SELECT ... LOCK IN SHARE MODE;
#或
SELECT ... FOR SHARE; #(8.0新增语法)
在普通的SELECT语句后边加LOCK IN SHARE MODE
,如果当前事务执行了该语句,那么它会为读取到的记录加S锁。
SELECT ... FOR UPDATE;
在普通的SELECT语句后边加FOR UPDATE
,如果当前事务执行了该语句,那么它会为读取到的记录加X锁。这样既不允许别的事务获取这些记录的S锁,也不允许获取这些记录的X锁。如果别的事务想获取这些记录的S锁或X锁,那么它们会阻塞,直到当前事务提交之后将这些记录上的X锁释放掉。
为了尽可能提高数据库的并发度,每次锁定的数据范围越小越好,理论上每次只锁定当前操作的数据的方案会得到最大的并发度,但是管理锁是很耗资源的事情(涉及获取、检查、释放锁等动作)。因此数据库系统需要在高并发响应
和系统性能
两方面进行平衡,这样就产生了“锁粒度
(Lock granularity)”的概念。
对一条记录加锁影响的也只是这条记录而已,我们就说这个锁的粒度比较细;其实一个事务也可以在表级别进行加锁,自然就被称之为表级锁或者表锁,对一个表加锁影响整个表中的记录,我们就说这个锁的粒度比较粗。锁的粒度主要分为表级锁、页级锁和行锁。
InnoDB 支持 多粒度锁
(multiple granularity locking) ,它允许 行级锁
与 表级锁
共存,而意向锁就是能和行级锁共存的一种 表锁
。
意向锁解决的问题是:
现在有两个事务,分别是T1和T2,其中T2试图在该表级别上应用共享或排它锁,如果没有意向锁存在,那么T2就需要去检查各个页或行
是否存在锁(假设表中数据有100000行,是很费时间的);如果存在意向锁,那么此时就会受到由T1控制的表级别意向锁的阻塞。T2在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁。简单来说就是给更大一级别的空间示意里面是否已经上过锁。
在数据表的场景中,如果我们给某一行数据加上了排它锁,数据库会自动给更大一级的空间,比如数据页或数据表加上意向锁,告诉其他人这个数据页或数据表已经有人上过排它锁了
,这样当其他人想要获取数据表排它锁的时候,只需要了解是否有人已经获取了这个数据表的意向排他锁即可。
意向锁分为两种:
-- 事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
SELECT column FROM table ... LOCK IN SHARE MODE;
-- 假设本次查询只对三条数据加了S锁,InnoDB会自动给该表加IS锁
-- 事务要获取某些行的 X 锁,必须先获得表的 IX 锁。
SELECT column FROM table ... FOR UPDATE;
即:意向锁是由存储引擎 自己维护的
,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行 所在数据表的对应意向锁
。
即意向锁之间是互相兼容的,虽然意向锁和自家兄弟互相兼容,但是它会与普通的排他/共享锁互斥。
注意这里的排他/共享锁指的都是表锁
,意向锁不会与行级的共享/排他锁互斥
。
IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的x,s发生冲突。
本次学习不做了解。