1. 加锁机制
1.1 悲观锁
synchronized:线程一旦获取到锁,其他需要锁的线程就挂起的情况就是悲观锁。
1.2 乐观锁
cas:每次不加锁而是假设没有冲突而去完成某项操作,如果冲突就重试,直到成功为止。
2. 锁粒度
2.1 表锁
表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。
该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小,所以获取锁和释放锁的速度很快,由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,大大降低并发度。
使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。
2.2 行锁
与表锁正相反,行锁最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力从而提高系统的整体性能。
虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
使用行级锁定的主要是InnoDB存储引擎。
适用场景: 从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新数据的情况,同时又有并发查询的应用场景。
2.3 页锁
是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。
页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
使用页级锁定的主要是BerkeleyDB存储引擎。
2.4 全局锁
对整个数据库实例加锁。使用场景一般在全库逻辑备份
时。
2.4.1 Flush tables with read lock (FTWRL)
这个命令可以使整个库处于只读状态
。使用该命令之后,数据更新语句、数据定义语句和更新类事务的提交语句等修改数据库的操作都会被阻塞。
风险:
- 如果在主库备份,在备份期间不能更新,业务停摆
- 如果在从库备份,备份期间不能执行主库同步的binlog,导致主从延迟同步
2.4.2 set global readonly=true
将整个库设置成只读状态,但这种修改global配置量级较重,和全局锁不同的是:如果执行Flush tables with read lock 命令后,如果客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态。但将库设置为readonly后,客户端发生异常断开,数据库依旧会保持readonly状态,会导致整个库长时间处于不可写状态
。
2.5 死锁产生的条件
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强⾏剥夺,只有自己使用完毕后才释放资源(资源未释放,资源一直在占用)。
- 循环等待条件:若干进程之间形成⼀种头尾相接的循环等待资源关系。
2.6 总结
行锁 | 页锁 | 表锁 | |
---|---|---|---|
InnoDB | 是 | 是 | |
MyISAM | 是 | ||
RDB | 是 | 是 | |
Oracle | 是 | 是 | |
SQL Server | 是 | 是 | 是 |
3. 兼容性
3.1 共享锁-S锁
共享锁就是多个事务对于同一数据共享一把锁,都能访问到数据,但是只能读不能修改。共享锁又称为读锁,简称S锁
。
3.2 排它锁-X锁
排他锁就是不能与其它锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。排他锁又称为写锁,简称X锁
。
原理:一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁(排他锁或者共享锁),即一个事务在读取一个数据行的时候,其他事务不能对该数据行进行增删改,不加锁的查是可以的,加锁的查是不可以的。
3.3 如何启用
1 | #设置共享锁 |
InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁(X锁)
select语句默认不会加任何锁类型,所以加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。
除此之外还有意向共享锁和意向排他锁,我将会在后面的意向锁中讲到
4. 锁模式
4.1 意向锁
意向锁属于表级锁,其设计目的主要是为了在一个事务中揭示下一行将要被请求锁的类型。
即:意向锁是有数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁。
4.1.1 意向共享锁(IS)
表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁;
4.1.2 意向排他锁(IX)
类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。
4.1.3 意向锁之间是互相兼容的(不互斥)
4.1.4 意向锁的意义
如果另一个任务试图在该表级别上应用共享或排它锁,则受到由第一个任务控制的表级别意向锁的阻塞。第二个任务在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁。
4.1.5 意向锁的并发性
意向锁不会与行级的共享 / 排他锁互斥,所以意向锁并不会影响到多个事务对不同数据行加排他锁时的并发性
4.1.6 总结
- InnoDB 支持多粒度锁,特定场景下,行级锁可以与表级锁共存。
- 意向锁之间互不排斥,但除了 IS 与 S 兼容外,意向锁会与 共享锁 / 排他锁 互斥。
- IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突。
- 意向锁在保证并发性的前提下,实现了行锁和表锁共存且满足事务隔离性的要求。
4.2 间隙锁 gap lock
一个在索引记录之间的间隙上的锁
4.2.1 间隙锁的作用
保证某个间隙内的数据在锁定情况下不会发生任何变化。
假设为隔离级别为RR
当使用唯一索引来搜索唯一行的语句时,不需要间隙锁定。
1 | select * from t where id = 10 for update; |
- 如果,上面语句中id列没有建立索引或者是非唯一索引时,则语句会产生间隙锁。
- 如果,搜索条件里有多个查询条件(即使每个列都有唯一索引),也是会有间隙锁的。
需要注意的是,当id列上没有索引时,SQL会走聚簇索引的全表扫描进行过滤,由于过滤是在MySQL Server层面进行的。因此每条记录(无论是否满足条件)都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁。但是不满足条件的记录上的加锁/放锁动作是不会省略的。所以在没有索引时,不满足条件的数据行会有加锁又放锁的耗时过程。
4.2.2 间隙锁的范围
根据检索条件向下寻找最靠近检索条件的记录值A作为左区间,向上寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)。
1 | select * from table where number = 6 for update; |
number | 1 | 2 | 3 | 4 | 5 | ==6== | ==6== | ==6== | 11 |
---|---|---|---|---|---|---|---|---|---|
id | 1 | 3 | 5 | 7 | 9 | 10 | 11 | 12 | 23 |
间隙锁是一种在数据库中用于处理并发事务的锁定方式。
它的作用是防止其他事务在间隙(两个索引键之间的空白区域)中插入新记录。
间隙锁通常用于防止幻读(Phantom Read)的问题,即在一个事务中多次执行同一查询时,查询的结果集合发生了变化。
页锁(Page Lock)是另一种锁定机制,它是在数据库页级别上对数据进行锁定。当一个事务锁定了一个数据页时,其他事务需要等待该事务释放锁才能继续对该页进行读取或写入操作。
页锁通常用于处理较大数据块的并发访问问题。
4.3 记录锁
1 | -- id 列为主键列或唯一索引列 |
记录锁是锁住记录,锁住索引记录,而不是真正的数据记录.
如果要锁的列没有索引,进行全表记录加锁(上面说过没有索引时SQL会走聚簇索引的全表扫描进行过滤)
记录锁也是排它(X)锁,所以会阻塞其他事务对其插入、更新、删除。
4.4 next-key锁
行锁+间隙锁
innoDB默认的隔离级别是可重复读(Repeatable Read),并且会以Next-Key Lock的方式对数据行进行加锁。Next-Key Lock是行锁和间隙锁的组合,当InnoDB扫描索引记录的时候,会首先对索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录。
当查询的索引含有唯一属性(唯一索引,主键索引)时,Innodb存储引擎会对next-key lock进行优化,将其降为record lock,即仅锁住索引本身,而不是范围。
4.5 插入意向锁
插入意向锁是一种间隙锁形式的意向锁,在真正执行 INSERT 操作之前设置。
当执行插入操作时,总会检查当前插入操作的下一条记录(已存在的主索引节点)上是否存在锁对象,判断是否锁住了 gap,如果锁住了,则判定和插入意向锁冲突,当前插入操作就需要等待,也就是配合上面的间隙锁或者临键锁一起防止了幻读操作。
(1)申请意向锁的动作是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。
(2)IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突
行级别的X和S按照普通的共享、排他规则即可。所以之前的示例中第2步不会冲突,只要写操作不是同一行,就不会发生冲突。
5. 总结
行锁(Row Lock)是针对数据库表中的行进行加锁的机制。当一个事务对某一行进行更新或删除操作时,会对该行加上行锁,防止其他事务同时对同一行进行操作,确保数据的一致性。
页锁(Page Lock)是针对数据库表中的页(通常是数据库中的一个数据页)进行加锁的机制。当一个事务对某一页中的多行进行操作时,可以选择对整个页进行加锁,减少锁的粒度,提高并发性能。
表锁(Table Lock)是针对整个数据库表进行加锁的机制。当一个事务需要对整个表进行操作时,可以选择对表进行加锁,防止其他事务对整个表进行操作,确保数据的一致性。但是表锁会降低并发性能,因为其他事务无法并发地对表中的不同行进行操作。
全局锁(Global Lock)是针对整个数据库进行加锁的机制。当需要对整个数据库进行备份、恢复或其他维护操作时,可以选择对数据库加上全局锁,防止其他事务对数据库进行操作。
S锁(Shared Lock)是一种共享锁,多个事务可以同时对同一资源加上S锁,读取资源但不进行修改。这样可以提高并发性能,允许多个事务同时读取同一资源。
X锁(Exclusive Lock)是一种独占锁,一个事务对某一资源加上X锁后,其他事务无法再对该资源进行读取或修改操作,直到该事务释放锁。
意向锁(Intent Lock)是一种锁的层次结构。当一个事务需要对某一行进行加锁时,会先获取意向锁,表明该事务将对该行进行加锁操作,同时其他事务可以获取表级别的锁,但不能获取同一行的行级别锁。
间隙锁(Gap Lock)是在索引范围内的间隙上设置的锁,用于防止其他事务在这个范围内插入新的记录。间隙锁可以避免幻读问题。
Next Key锁(Next-Key Lock)是InnoDB存储引擎中一种组合锁,结合了行锁和间隙锁的特性。它在行锁和间隙锁之间建立了一个边界,保证了范围查询的正确性,并避免了幻读问题。
记录锁(Record Lock)是行级别的锁,用于保护单个记录。当一个事务对某一行进行修改或删除操作时,会对该行加上记录锁,防止其他事务同时对同一行进行操作。
插入意向锁(Insert Intention Lock)是意向锁的一种,用于在插入新记录时保护间隙。当一个事务在某个间隙内进行插入操作时,会先获取插入意向锁,表明该事务将在该间隙内插入新记录,防止其他事务在同一间隙内插入新记录。