MySQL中锁按照粒度划分主要分为全局锁、表级锁、行级锁。
全局锁
全局锁主要用于全库逻辑备份。加锁期间整库处于只读状态,数据和表定义的修改都会被禁止。
全局读锁:flush tables with read lock
释放全局锁:unlock tables
全局锁的使用比较危险,不建议使用。如果备份数据库建议使用如InnoDB这样的支持事务的引擎,用工具mysqldump,加参数--single-transaction
,可以利用MVCC提供的一致性视图,不使用全局锁,在不影响业务正常运行下进行备份;而对于不支持事务的引擎,需通过工具mysqldump加参数--lock-all-tables
,加全局锁获得一致性视图。
表级锁
表级锁主要有下面四种:
- 表锁
- 元数据锁
- 意向锁
- AUTO-INC 锁
表锁
读锁 : lock tables t read
写锁 : lock tables t write
释放表锁 : unlock tables
元数据锁 Metadata Locks
当我们对数据库表进行操作时,会自动给这个表加上 MDL:
- DML语句–> MDL 读锁
- DDL语句–> MDL 写锁
元数据锁的目的是保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。
但是如果数据库有一个长事务,那在对表结构做变更操作的时候,可能会发生长时间阻塞的问题。例如A线程是长事务执行了select语句申请了MDL读锁,此时线程B进行修改表结构,申请MDL写锁堵塞。 后面C…等线程再进行select语句都将被堵塞。也就是说当发生MDL写锁阻塞时,后续如果有大量该表的select语句,会有大量的线程被阻塞住,从而数据库的线程很快就爆满了。
所以为了能安全的对表结构进行变更,在对表结构变更前,先要看看数据库中的长事务,是否有事务已经对表加上了 MDL 读锁,可以考虑 kill 掉这个长事务,然后进行表结构变更。
意向锁 Intention Locks
意向锁分为意向共享锁和意向排它锁。
- 意向共享锁(IS),表示事务准备在表的某些记录上加上共享锁。
- 意向排它锁(IX),表示事务准备在表的某些记录上加上排它锁。
例如,当执行插入、更新、删除操作或者SELECT ... FOR UPDATE
,需要先对表加上「意向排它锁」,然后对该记录加排它锁。而SELECT ... LOCK IN SHARE MODE
会对表加上一个意向共享锁
意向共享锁和意向排它锁是表级锁,不会和行级的共享锁和排它锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁和排它表锁发生冲突。
各种表级锁的兼容性如下:
X | IX | S | IS | |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
**意向锁的主要目的是表明有其它事务正在锁定或者将要锁定表格中的某些行。**可以用来快速判断表里是否有记录被加锁,如果没有意向锁,那么加排它表锁时,就需要遍历表里所有记录,查看是否存在排它锁, 而有了意向锁后可以直接判断意向锁,提高效率。
自增锁 AUTO-INC Locks
自增锁是一个特殊的表级锁,向带有自增字段的表格插入数据时会用到。
那么一个事务在持有 AUTO-INC 锁时,其他事务的如果要向该表插入语句都会被阻塞。所以AUTO-INC 锁在大量数据进行插入的时候,会影响插入性能。可以通过innodb_autoinc_lock_mode
参数可以控制自增锁的算法,你可以选择更好的并发性,但是放弃自增字段的连续性。
- 当 innodb_autoinc_lock_mode = 0,就采用 AUTO-INC 锁,语句执行结束后才释放锁;
- 当 innodb_autoinc_lock_mode = 2,就采用轻量级锁,申请自增主键后就释放锁,并不需要等语句执行后才释放。
- 当 innodb_autoinc_lock_mode = 1:
- 普通 insert 语句,自增锁在申请之后就马上释放;
- 类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放;
当 innodb_autoinc_lock_mode = 2 是性能最高的方式,但是当搭配 binlog 的日志格式是 statement 一起使用的时候,在「主从复制的场景」中会发生数据不一致的问题。当innodb_autoinc_lock_mode = 2 时,建议配合binlog_format = row,既能提升并发性,又不会出现数据一致性。
行级锁
InnoDB 引擎支持行级锁的,而 MyISAM 引擎不支持行级锁。
共享锁(S锁)满足读读共享,读写互斥。排它锁(X锁)满足写写互斥、读写互斥。
行级锁主要有三种:
- Record Lock,记录锁,锁住一条记录;
- Gap Lock,间隙锁,锁定一个范围,但不包含记录本身;
- Next-Key Lock:临键锁, 锁定一个范围,包括记录本身。
记录锁 Record Locks
记录锁,锁住的是一条记录。分为S 锁和 X 锁。
兼容性如下:
X | S | |
---|---|---|
X | 冲突 | 冲突 |
S | 冲突 | 兼容 |
例如当通过主键id进行更新时会这条记录加上 X 型的记录锁,这样其他事务就无法对这条记录进行修改了。
间隙锁 Gap Locks
间隙锁用于锁住索引之间的间隙,或者索引第一条记录之前、最后一条记录之后的区间。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
语句会阻止其它事务插入t.c1=15
的记录,无论目前表中有没有t.c1=15
的记录,因为间隙锁会锁住已有记录之间的间隙。
间隙锁是用于权衡性能和并发性的一个方案,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的问题。
当使用唯一索引来查找一行唯一的记录时,不会用到间隙锁。(这不包括搜索条件仅包括多列唯一索引的某些列的情况;在这种情况下,确实会发生间隙锁定。)例如,如果列id
有唯一索引,则以下语句仅使用索引记录锁定值为 100 的行id
。如果id
未建立索引或具有非唯一索引,则该语句会锁定前面的间隙。
间隙锁唯一的用处是:阻止其它事务往间隙中插入记录。间隙锁可以共存,事务获取到一个间隙锁后,并不会阻止其它事务获取同一个间隙的间隙锁。共享间隙锁和排它间隙锁没有区别,它们也不会互相阻塞。
临键锁 Next-Key Locks
临键锁是索引上的记录锁,和索引之前的间隙锁的组合。
InnoDB加行级锁的方式是当它查找或者扫描索引时,给查找过程中遇到的记录加上排它锁或共享锁,因此行级锁其实就是记录锁。而加在索引记录上的临键锁还会对索引记录之前的间隙有影响。也就是说临键锁是一个记录锁加上索引之前的间隙锁。如果一个事务在记录R
上加上了共享锁或者排它锁,其它事务就无法在R
之前的间隙插入数据。
假设一个索引现在已有的值为:10, 11, 13和20, 那么临键锁可能锁定的区间:
(-∞, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +∞)
默认情况下,Innodb使用可重复读(REPEATABLE READ)隔离级别。在这个隔离级别下,InnoDB会在查找过程中给遇到的记录加上临键锁,这样可以防止幻读。
插入意向锁 Insert Intention Locks
插入意向锁是一种间隙锁,在INSERT
语句插入数据之前会加上插入意向锁。插入意向锁的作用是向同一个索引间隙中插入数据的事务如果它们没有插入到间隙中的相同位置,就不需要互相等待。假设现在索引中有4和7两个值,有2个事务分别想插入5和6,则每个事务在获得插入行的排它锁之前,都会使用插入意向锁锁住4~7之间的间隙,但是插入意向锁并不会互相阻塞,因为2个事务插入的是不同的位置。
如果有的话,插入操作就会发生阻塞,直到拥有间隙锁的那个事务提交为止(释放间隙锁的时刻),在此期间会生成一个插入意向锁,表明有事务想在某个区间插入新记录,但是现在处于等待状态。
插入意向锁名字虽然有意向锁,但是它并不是意向锁,它是一种特殊的间隙锁,锁住的就是一个点,属于行级别锁。
参考
MySQL官方文档:InnoDB Locking