1、MySQL有哪些锁?
全局锁:flush tables with read lock 执行以下语句之后,使用全局锁,整个数据库就处于只读状态了,这时其他线程执行对数据的增删改或者对表结构的更改操作操作,都会被阻塞。
全局锁的应用场景:全局锁主要应用于做全库逻辑备份,这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样。
全局锁的缺点 :业务只能读数据,而不能更新数据,这样会造成业务停滞。
备份数据库数据的时候,使用全局锁会影响业务,那有什么其他方式可以避免?
如果数据库的引擎支持的事务
支持可重复读的隔离级别,那么在备份数据库之前先开启事务,会先创建 Read View,然后整个事务执行期间都在用这个 Read View,而且由于 MVCC 的支持,备份期间业务依然可以对数据进行更新操作。
表级锁:
表锁
对学生表(t_student)加表锁,可以使用下面的命令:
//表级别的共享锁,也就是读锁;
lock tables t_student read;
//表级别的独占锁,也就是写锁;
lock tables t_stuent write;
表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。
也就是说如果本线程对学生表加了「共享表锁」,那么本线程接下来如果要对学生表执行写操作的语句,是会被阻塞的,当然其他线程对学生表进行写操作时也会被阻塞,直到锁被释放。
元数据锁(MDL)
当对数据库表进行操作时,会自动给这个表加上 MDL:
对一张表进行 CRUD 操作时,加的是
MDL 读锁;
对一张表做结构变更操作的时候,加的是
MDL 写锁;
MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。MDL 是在事务提交后才会释放,
这意味着事务执行期间,MDL 是一直持有的。MDL 锁的操作会形成一个队列,
队列中写锁获取优先级高于读锁,一旦出现 MDL 写锁等待,会阻塞后续该表的所有 CRUD 操作。
所以为了能安全的对表结构进行变更,在对表结构变更前,先要看看数据库中的长事务,是否有事务已经对表加上了 MDL 读锁,如果可以考虑 kill 掉这个长事务,然后再做表结构的变更。
意向锁
意向锁的使用场景:
在使用 InnoDB 引擎的表里对某些
记录加上「共享锁」之前(行锁),需要先在表级别加上一个「意向共享锁」;
在使用 InnoDB 引擎的表里对某些
纪录加上「独占锁」之前(行锁),需要先在表级别加上一个「意向独占锁」;
当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。而普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的。不过,select 也是可以对记录加共享锁和独占锁的,具体方式如下:
//先在表上加上意向共享锁,然后对读取的记录加共享锁
select ... lock in share mode;
//先表上加上意向独占锁,然后对读取的记录加独占锁
select ... for update;
意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(lock tables … read)和独占表锁(lock tables … write)发生冲突。
表锁和行锁是满足读读共享、读写互斥、写写互斥的。
如果没有「意向锁」,那么加「独占表锁」时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。
那么有了「意向锁」,由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录。
所以,
意向锁的目的是为了快速判断表里是否有记录被加锁。
AUTO-INC锁
在为某个字段声明 AUTO_INCREMENT 属性时,之后可以在插入数据时,可以不指定该字段的值,数据库会自动给该字段赋值递增的值,这主要是通过以下两种方式实现
AUTO-INC 锁实现
AUTO-INC 锁是特殊的表锁机制,
锁不是再一个事务提交后才释放,而是再执行完插入语句后就会立即释放。在插入数据时,会加一个表级别的 AUTO-INC 锁,然后为被 AUTO_INCREMENT 修饰的字段赋值递增的值,等插入语句执行完成后,才会把 AUTO-INC 锁释放掉。那么,一个事务在持有 AUTO-INC 锁的过程中,其他事务的如果要向该表插入语句都会被阻塞,从而保证插入数据时,被 AUTO_INCREMENT 修饰的字段的值是连续递增的。
一种轻量级的锁来实现自增。
一样也是在插入数据的时候,会为被 AUTO_INCREMENT 修饰的字段加上轻量级锁,
然后给该字段赋值一个自增的值,就把这个轻量级锁释放了,而不需要等待整个插入语句执行完后才释放锁。
InnoDB 存储引擎提供了个 innodb_autoinc_lock_mode 的系统变量,是用来控制选择用 AUTO-INC 锁,还是轻量级的锁。
当 innodb_autoinc_lock_mode = 0,采用 AUTO-INC 锁;
当 innodb_autoinc_lock_mode = 2,采用轻量级锁;
当 innodb_autoinc_lock_mode = 1,默认值,两种锁混用,如果能够
确定插入记录的数量就采用轻量级锁,不确定时就采用 AUTO-INC 锁。
AUTO-INC 锁在对大量数据进行插入的时候,会影响插入性能,因为另一个事务中的插入会被阻塞。轻量级锁是性能最高的方式,但是会带来一定的问题。因为并发插入的存在,在每次插入时
自增长的值可能不是连续的,
这在有主从复制的场景中是不安全的
。
行级锁
InnoDB 引擎支持行级锁的,而 MyISAM 引擎并不支持行级锁。
Record Lock,记录锁
,锁住的是一条记录。记录锁是有
S 锁(共享)
和
X 锁(独占)
之分
Gap Lock,间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。间隙锁虽然存在 X 型间隙锁和 S 型间隙锁,但是并没有什么区别,间隙锁之间是兼容的,即两个事务可以同时持有包含共同间隙范围的间隙锁,并不存在互斥关系,因为间隙锁的目的是防止插入幻影记录而提出的。
Next Lock,临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。如果一个事务获取了 X 型的 next-key lock,那么另外一个事务在获取相同范围的 X 型的 next-key lock 时,是会被阻塞的。
插入意向锁,一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁(next-key lock 也包含间隙锁)。如果有的话,
插入操作就会发生阻塞
,直到拥有间隙锁的那个事务提交为止(释放间隙锁的时刻),在此期间会生成一个
插入意向锁
,表明有事务想在某个区间插入新记录,但是现在处于等待状态。
它并不是意向锁,它是一种特殊的间隙锁,属于行级别锁。可以看成一种特殊的间隙锁。
2、MySQL是怎么加锁的?
对记录加锁时,
加锁的基本单位是 next-key lock
,它是由
记录锁和间隙锁
组合而成的,
next-key lock 是前开后闭区间
,
而间隙锁是前开后开区间。
但是,
next-key lock 在一些场景下会退化成记录锁或间隙锁。
唯一索引等值查询
当查询的记录是存在的,在用「唯一索引等值进行查询」时,next-key lock 会退化成「记录锁」。
当查询的记录是不存在的,在用「唯一索引等值进行查询」时,next-key lock 会退化成「间隙锁」。
唯一索引范围查询
先生成next-key lock,再看是否退化。
非唯一索引等值查询
当查询的记录存在时,除了会加 next-key lock 外,还额外加间隙锁,也就是会加两把锁。
当查询的记录不存在时,只会加 next-key lock,然后会退化为间隙锁,也就是只会加一把锁。
非唯一索引范围查询
生成next-key lock,并且不会退化。
非唯一索引和主键索引的范围查询的加锁规则不同之处在于:
唯一索引在满足一些条件的时候,next-key lock 退化为间隙锁和记录锁。
非唯一索引范围查询,next-key lock 不会退化为间隙锁和记录锁。
3、update没加索引会锁全表吗?
当在数据量非常大的数据库表执行 update 语句时,如果没有使用索引,就会给全表的加上 next-key 锁, 那么锁就会持续很长一段时间,直到事务结束,而这期间除了 select ... from语句,其他语句都会被锁住不能执行,业务会因此停滞。
但并不是update 语句的 where 带上索引就能避免全表记录加锁,关键还得看这条语句在执行过程种,优化器最终选择的是索引扫描,还是全表扫描,如果走了全表扫描,就会对全表的记录加锁了。
4、MySQL死锁了,怎么办?
死锁的四个必要条件:
互斥、占有且等待、不可强占用、循环等待
。只要系统发生死锁,这些条件必然成立,但是只要破坏任意一个条件就死锁就不会成立。
在数据库层面,有两种策略通过「打破循环等待条件」来解除死锁状态:
设置事务等待锁的超时时间。当一个事务的等待时间超过该值后,就对这个事务进行回滚,于是锁就释放了,另一个事务就可以继续执行了。在 InnoDB 中,参数 innodb_lock_wait_timeout 是用来设置超时时间的,默认值时 50 秒。
开启主动死锁检测
。主动死锁检测在发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数
innodb_deadlock_detect
设置为 on,表示开启这个逻辑,默认就开启。
上面这个两种策略是「当有死锁发生时」的避免方式。
我们可以回归业务的角度来预防死锁,对订单做幂等性校验的目的是为了保证不会出现重复的订单,那我们可以直接将 order_no 字段设置为唯一索引列,利用它的唯一下来保证订单表不会出现重复的订单,不过有一点不好的地方就是在我们插入一个已经存在的订单记录时就会抛出异常。
5、表级锁和行级锁有什么区别?
MyISAM 仅仅支持表级锁(table-level locking),一锁就锁整张表,这在并发写的情况下性非常差。InnoDB 不光支持表级锁(table-level locking),还支持行级锁(row-level locking),默认为行级锁。
行级锁的粒度更小,仅对相关的记录上锁即可(对一行或者多行记录加锁),所以对于并发写入操作来说, InnoDB 的性能更高。
表级锁和行级锁对比:
-
表级锁: MySQL 中锁定粒度最大的一种锁(全局锁除外),是针对非索引字段加的锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。不过,触发锁冲突的概率最高,高并发下效率极低。表级锁和存储引擎无关,MyISAM 和 InnoDB 引擎都支持表级锁。
-
行级锁: MySQL 中锁定粒度最小的一种锁,是 针对索引字段加的锁 ,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。行级锁和存储引擎有关,是在存储引擎层面实现的。
6、共享锁和排他锁?
不论是表级锁还是行级锁,都存在共享锁(Share Lock,S 锁)和排他锁(Exclusive Lock,X 锁)这两类:
-
共享锁(S 锁):又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
-
排他锁(X 锁):又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。
排他锁与任何的锁都不兼容,共享锁仅和共享锁兼容。
由于 MVCC 的存在,对于一般的
SELECT
语句,InnoDB 不会加任何锁。不过, 你可以通过以下语句显式加共享锁或排他锁。
7、意向锁有什么作用呢?
如果需要用到表锁的话,如何判断表中的记录没有行锁呢,一行一行遍历肯定是不行,性能太差。我们需要用到一个叫做意向锁的东东来快速判断是否可以对某个表使用表锁。
意向锁是表级锁,共有两种:
-
意向共享锁(Intention Shared Lock,IS 锁):事务有意向对表中的某些记录加共享锁(S 锁),加共享锁前必须先取得该表的 IS 锁。
-
意向排他锁(Intention Exclusive Lock,IX 锁):事务有意向对表中的某些记录加排他锁(X 锁),加排他锁之前必须先取得该表的 IX 锁。
意向锁是由数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享/排他锁之前,InnoDB 会先获取该数据行所在在数据表的对应意向锁。
意向锁之间是互相兼容的。
意向锁和共享锁和排它锁互斥(这里指的是表级别的共享锁和排他锁,意向锁不会与行级的共享锁和排他锁互斥)。
8、当前读和快照读有什么区别?
快照读
(一致性非锁定读)就是单纯的
SELECT
语句,但不包括下面这两类
SELECT
语句:
SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE
快照即记录的历史版本,每行记录可能存在多个历史版本(多版本技术)。
快照读的情况下,如果读取的记录正在执行 UPDATE/DELETE 操作,读取操作不会因此去等待记录上 X 锁的释放,而是会去读取行的一个快照
只有在事务隔离级别 RC(读取已提交) 和 RR(可重读)下,InnoDB 才会使用一致性非锁定读:
-
在 RC 级别下,对于快照数据,一致性非锁定读总是读取被锁定行的最新一份快照数据。
-
在 RR 级别下,对于快照数据,一致性非锁定读总是读取本事务开始时的行数据版本。
快照读比较适合对于数据一致性要求不是特别高且追求极致性能的业务场景。
当前读 (一致性锁定读)就是给行记录加 X 锁或 S 锁。
当前读的一些常见 SQL 语句类型如下: