MySQL的各种锁

MySQL的各种锁_第1张图片

MySQL的各种锁 

1. 锁的概述

  • 锁是计算机协调多个进程或线程并发访问某一资源的机制!
  • 锁是在并发访问时,为了保证数据库中数据的一致性,使各种 共享资源 在被访问时变得 有序 而设计的一种原则!
  • 我们主要研究InnoDB存储引擎,因为它既支持行锁,也支持表锁!
  • MySQL中不同的存储引擎支持不同的锁机制,InnoDB支持行锁,有时也会升级为表锁,MyISAM只支持表锁!
  • [表锁]的特点就是开销小,加锁快,不会出现死锁。锁粒度大,发生锁冲突的概率小,但并发度相对低。
  • [行锁]的特点就是开销大、加锁慢,会出现死锁。锁粒度小,发生锁冲突的概率高,但并发度高!

1.1 锁的分类 

MySQL中的锁,根据锁的粒度,分为以下三类:

  1. 全局锁:全局锁就是锁定数据库中所有的表,即锁住的是整个数据库实例,全局锁主要应用于做数据备份。全局锁锁的粒度是最大的!
  2. 表级锁:每次操作锁住整张表。
  3. 行级锁:每次操作锁住对应的行数据。行级锁锁的粒度是最小的!

2. 全局锁 

  • 全局锁是三种锁当中锁的粒度最大的一种锁!是一个比较重的操作! 
  • 全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,性能较差,即只能读不能写,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将会被阻塞!
  • 全局锁最典型的使用场景就是做全库的数据备份!数据备份指的就是将我们数据库当中的数据备份成一个SQL文件备份到磁盘当中!
  • mysqldump是MySQL数据库当中提供的一个用于数据备份的工具!
为什么在做全库数据备份时要锁定整个数据库当中所有的表呢?
  • 这是为了保证数据的一致性和完整性!否则将出现数据不一致!

在InnoDB引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致
性数据备份。

mysqldump --single-transaction -uroot –p123456 itcast > itcast.sql

语法:

  • 加全局锁
-- 加全局锁
flush tables with read lock;
  • 释放锁
-- 释放锁
unlock tables;

3. 表级锁

  • 表级锁,每次操作锁住整张表,锁定粒度大,发生锁冲突的概率最高,并发度最低,应用在MyISAM、InnoDB等存储引擎中。

表级锁,主要分为以下三类:

  1. 表锁:表锁会锁住整张表,粒度较大!
  2. 元数据锁(Meta Data Lock,简称MDL)
  3. 意向锁

3.1 表锁

对于表锁,又分为两类:
  1. 读锁(Read Lock) - 表共享读锁 - 共享锁 - Shared Lock - 简称s锁
  2. 写锁(Write Lock)表独占写锁 - 排它锁 - Exclusive(独占的)- 简称x锁

加表锁,即读锁或写锁的语法:当我们对某一张表加上读锁或写锁时,此时它也会自动的加上对应的MDL元数据锁~!

  • 加锁:lock tables 表名1,表名2...  read / write;
  • 释放所报:unlock tables / 客户端断开连接;

3.1 表锁 

  1. 读锁

  • 读锁(共享锁,Shared Lock),简称S锁,读锁是共享锁,多个事务可以同时持有,当有一个或多个事务持有共享锁时,被锁的数据就不能修改!
  • 一个事务获取到了一个数据行的读锁之后,其它事务也能获取到该数据行对应的读锁,但不能获得写锁,即一个事务在读取一个数据行时,其它事务也可以读,但是不能对该数据行进行写操作(即增删改操作)!即可以多个事务读,但是只能一个事务写!
  • 读锁的 lock_type 元数据锁锁的类型为SHAERED_READ_ONLY!
  • 结论:读锁不会阻塞其他客户端的读,但是会阻塞其他客户端的写。

  2.

  • 写锁,也叫排他锁或者独占锁,简称x锁(Exclusive),只能有一个事务可以持有,当这个事务持有写锁的时候,被锁的数据就不能被其它事务修改。
  • 一个事务如果获取到了一个数据行的写锁,那么这个事务既可以读取该行的行记录,也可以修改该行的行记录,但是其它事务就不能获取该行的其它任何的锁,包括s锁,直到当前事务将锁释放,这样保证了其它事务在当前事务释放锁之前不能再修改该数据
  • 写锁的 lock_type 元数据锁的锁类型为SHARED_NO_READ_WRITE!
  • 结论:写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写~!
  • 当事务提交了,锁就会被释放~!  

3.2 元数据锁metadata_locks(Meta Data Lock - MDL)

  • MDL元数据锁在加锁的过程当中它是系统自动控制的,我们无需显式去使用 lock 这样的关键字去手动加锁,当我们在访问一张表时会自动加上MDL元数据锁。
  • 元数据就是描述数据的数据,也就是你的表结构,MDL元数据锁主要作用就是维护表结构的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。简单说就是如果某一张表存在未提交的事务,那么我们不能去修改这张表的表结构!元数据锁的存在就是为了避免DML和DDL冲突,保证数据读写的正确性~!
  • 在MySQL 5.5 中引入了MDL元数据锁,当对一张表进行增删改查的时候,它会自动的加上MDL元数据锁当中的共享读锁或共享写锁(是一把共享锁)- 元数据共享锁(SHARED_READ / SHARED_WRITE);当对表结构进行变更操作的时候,它会自动的加上MDL写锁 - 元数据排它锁(是一把排它锁)- EXCLUSIVE!
常见的SQL操作时,所添加的元数据锁: 

MySQL的各种锁_第2张图片

  • 当我们执行select语句时,它会自动的加上元数据锁当中的共享的读锁(SHARED_READ)!
  • 通过 select ... lock in share mode 语句将会给被读取的行记录或行记录的范围上加元数据锁当中的一个共享的读锁(SHARED_LOCK),让其它事务可以读,但是如果要想申请写锁,那就会被阻塞!除非所有的读事务全部释放了读锁之后,才能申请到写锁!  
  • 当我们执行insert、update、delete语句会对行记录加上元数据锁当中的共享的写锁(SHARED_WRITE)!
  • 比较特殊的就是 select ... for update,它也会对读取的行记录加上一个元数据锁当中的共享的写锁(SHARED_WRITE),写锁只能被一个事务获取,一旦该事务获取到了该行记录的写锁,那么其它任何事务不能对被锁定的行上加任何锁了,要不然会被阻塞~!

这种在查询时会加锁的语句称为锁定读~! 

-- 对读取的行记录加上元数据锁当中的读锁(SHARED_LOCK)
select ... lock in share mode;

-- 对读取的行记录加上元数据锁当中的写锁(SHARED_WRITE)
select ... for update;
  • 当我们执行 alter table 这样的语句的时候,也就意味着我们要去修改表结构了,那么此时它所加的是MDL写锁 - 元数据锁当中的排他锁(EXCLUSIVE)!这种元数据锁与所有其他的MDL元数据锁都是互斥的!
我们可以通过下面的SQL,来查看数据库中的元数据锁的情况:查询系统表当中的metadata_locks这张表,在metadata_locks这张表当中就记录了我们当前数据库实例当中的元数据锁!
-- 查看元数据锁的加锁情况
SELECT
	object_type,
	object_schema,
	object_name,
	lock_type,
	lock_duration 
FROM
	PERFORMANCE_SCHEMA.metadata_locks;

3.3 意向锁

  • 为了避免DML语句在执行的时候,加的行锁与表锁的冲突问题,因此在InnoDB引擎当中引入了意向锁,使得在加表锁时不用检查每行数据是否加锁,使用意向锁来减少表锁的检查!
  • 在MySQL默认的RR隔离级别下,执行 update 语句它会根据主键进行更新,此时它会自动的对这行数据加上行锁!
  • 在执行DML语句时,会对涉及到的行加行锁!
  • 假如没有意向锁,客户端一对表加了行锁之后,此时客户端二想要对该表加表锁,那么客户端二想要对这张表加表锁时,就会去检查当前表是否有所对应的行锁,此时就会从第一行数据,检查到最后一行数据,效率较低,如果没有,则添加表锁!
  • 而有了意向锁之后,在执行DML操作时,会对涉及到的行加行锁,同时也会对该表加上意向锁!而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,通过意向锁的情况来决定能不能加表锁,而不用逐行去判断行锁情况了!如果当前表的意向锁和所要加的表锁兼容,则直接加锁;如果当前表的意向锁和所要加的表锁不兼容,则表锁这一方就会处于阻塞状态,阻塞到加行锁的这一方事务提交,把行锁释放、意向锁释放之后,就会解除阻塞状态,从而拿到这张表的表锁!

MySQL的各种锁_第3张图片

意向锁又分为两种:
  • 意向共享锁(IS):由语句select ... lock in share mode添加,与表锁当中的读锁 - 共享锁(read)是兼容的,与表锁当中的写锁 - 排它锁(write)互斥!
  • 意向排它锁(IX):由语句insert、update、delete、select ... for update添加,与表锁当中的读锁 - 共享锁(read)以及表锁当中的写锁 - 排他锁(write)都互斥,意向锁之间不会互斥!
  • 一旦事务提交了,意向共享锁、意向排他锁都会自动释放!!!
可以通过以下SQL,查看意向锁以及行锁的加锁情况:
-- 查看意向锁及行锁的加锁情况	
SELECT
	object_schema,
	object_name,
	index_name,
	lock_type,
	lock_mode,
	lock_data 
FROM
	PERFORMANCE_SCHEMA.data_locks;

4. 行级锁 

  • 行级锁指的是每一次加锁操作,它锁住的是对应的数据行,并不是去锁表。
  • 对于行级锁来说,锁的粒度最小,发生锁冲突的概率最低,并发度最高,只有在InnoDB存储引擎当中才支持行级锁!
  • InnoDB引擎的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,行锁是加在索引上的,即行锁的内容是锁在索引上的,没有索引就是表锁!
对于InnoDB引擎当中的行级锁,主要分为以下三类:
  1. Record Lock - 行锁或记录锁:记录锁就是我们常说的行锁,锁定单个行记录的锁,防止其它事务对此进行update或delete操作,在 RC 和 RR 隔离级别下是支持行锁的!只有InnoDB才支持行锁!即InnoDB会锁行!
  2. 间隙锁(Gap Lock):什么是间隙?间隙指的就是两个记录之间的范围!间隙锁指的就是锁住索引记录的间隙(但是间隙锁它不包含该数据记录本身,它只锁中间的间隙),也就是不允许在某个范围内插入数据。间隙锁它保证的是索引记录的间隙不变,防止其它事务在这个间隙进行insert,从而产生幻读现象!间隙锁只存在于RR可重复读隔离级别下!目的是为了解决RR可重复读隔离级别下幻读的现象!
  3. 临建锁(Next-Key Lock):是Record Lock行锁 和 Gap Lock间隙锁 的组合,锁定一个范围,并且锁定记录本身!也就是会同时锁住记录或数据,并同时锁住数据前面的间隙Gap。在RR隔离级别下支持Next-Key Lock临建锁!如果把事务的隔离级别降低为RC,临建锁将会失效~!

4.1 行锁 

在InnoDB存储引擎当中,提供了以下两种类型的行锁:

  1. 共享锁(S锁)- Share:允许一个事务去读取一行,但阻止其它事务获得相同数据集的排他锁!即共享锁和共享锁之间是兼容的,但是共享锁和排他锁之间是互斥的!
  2. 排他锁(X锁)- Exclusive:允许获取排他锁的事务执行更新语句,但阻止其它事务获得相同数据集的共享锁和排他锁!假如第一个事务获取到了某一行数据的排他锁,那么其它的事务就不能再获取这行数据的共享锁及排他锁! 
两种行锁的兼容情况如下:
MySQL的各种锁_第4张图片
常见的SQL语句,在执行时,所加的行锁的类型如下:
SQL 行锁类型 说明
INSERT ... 排他锁 自动加锁
UPDATE ... 排他锁 自动加锁
DELETE ... 排他锁 自动加锁
SELECT(正常) 不加任何锁
SELECT ... LOCK IN SHARE
MODE
共享锁 需要手动在SELECT之后加LOCK IN SHARE
MODE
SELECT ... FOR UPDATE 排他锁 需要手动在SELECT之后加FOR UPDATE
  • 默认情况下,InnoDB在Repeatable Read事务隔离级别下运行,InnoDB使用next-key 临建锁进行搜索和索引扫描,以防止幻读!
  1. 针对唯一索引进行检查时,对已存在的记录进行等值匹配时,将会自动优化为行锁!
  2. InnoDB的行锁是针对于索引加的锁,只有你这一行数据的检索条件有了索引,它才能支持行级锁!如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,也就是会升级为表锁!(比如update语句)
  3. 结论:我们根据索引字段进行更新操作,就可以避免行锁升级为表锁的情况!
可以通过以下SQL,查看意向锁以及行锁的加锁情况:
-- 查看意向锁及行锁的加锁情况	
SELECT
	object_schema,
	object_name,
	index_name,
	lock_type,
	lock_mode,
	lock_data 
FROM
	PERFORMANCE_SCHEMA.data_locks;

4.2 Gap Lock间隙锁 & Next-Key Lock临建锁

  • 默认情况下,InnoDB在Repeatable Read事务隔离级别下运行,InnoDB使用 next - key临建锁进行搜索和索引扫描,以防止出现幻读。
  • 索引上的等值查询(唯一索引 => 主键索引不就是唯一索引吗?),即使用唯一索引进行等值查询时,给不存在的记录加锁时,优化为间隙锁!
  • 索引上的等值查询(非唯一的普通索引),向右遍历时最后一个值不满足查询要求时,next-key临建锁退化为间隙锁!
  • 索引上的范围查询(唯一索引) -- 会访问到不满足条件的第一个值为止! 
  • 间隙锁锁定的时记录范围,不包含记录本身,也就是不允许在某个范围内插入数据~!
  • 间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁!

一. InnoDB的锁类型

  • InnoDB的锁类型主要有读锁(共享锁)、写锁(排他锁)、意向锁和MDL锁。

1. MySQL有遇到过死锁的问题吗?你是如何解决的?

MySQL的各种锁_第5张图片

  • 死锁,就是两个或两个以上的线程在执行过程中,去争夺同一个共享资源导致互相等待的现象,在没有外部干预的情况下,线程会一直处于阻塞状态,无法往下执行!
  • 出现死锁以后,可以通过jstack命令去导出线程的dump日志,然后从dump日志里面定位到具体死锁的程序代码,然后通过修改程序代码区破坏请求和保持条件、破坏循环等待条件、破坏不可剥夺条件这三个条件里面的任意一个,就可以解决死锁问题。
  • 当然,因为互斥条件是锁本身的特性,所以不嫩被破坏!

你可能感兴趣的:(mysql,数据库,java,架构)