锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除了传统的计算资源(CPU、RAM、i/O)的挣用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素,从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
MySQL中的锁、按照锁的粒度分,分为以下三类
全局锁就是对整个数据库实例枷锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,以及更新操作的事物提交语句都会被阻塞,其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
flush tables with read lock;
unlock tables;
说明1:客户端1中设置了全局锁
说明2:客户端2中,执行查询语句正常的,但是执行DML语句中的更新操作却是处于阻塞状态
说明1:客户端1中设置了全局锁
说明2:客户端3中做了数据库备份的语句,其中mysqldump是和mysql一样由MySQL服务器提供的数据库备份的命令
说明3:当数据库设置了全局锁的时候,不影响数据库的备份
说明4:MySQL备份实在终端命令行模式下,不是在数据库命令模式下,注意!注意!注意!
说明1:客户端1释放全局锁
说明2:客户端2的更新语句马上执行成功
说明3:通过客户端2的更新语句的执行时间37min,可以说明该语句被客户端1的全局锁,阻塞了37分钟
数据库中加全局锁,是一个比较重的操作,存在以下问题
在innoDB引擎中,我们可以在备份时加上 --single-transaction 参数来完成不加锁的一致性数据备份
表级锁,每次操作锁住整张表,锁定粒度大,发生锁冲突的概率最高,并发度最低,应用在MyISAM,InnoDB等存储引擎中
加锁:lock tables 表名... read/write
释放锁:unlock tables / 客户端断开连接
说明1:加锁的时候,可以多张表同时加锁
说明2:客户端断开连接也能释放表锁
说明1:当对一个表添加读锁,不会影响其读取数据,但是会影响其新增,修改,删除的操作语句
说明2:当对一个表添加读锁,不会影响其他客户端读取数据,但是会让其他客户端的新增,修改,删除等操作语句处于阻塞状态。
说明3:当把锁释放了,自己及其他客户端的新增,删除和修改语句才会结束阻塞。
说明1:添加写锁的客户端可以正常对表进行查询和增删改等操作
说明2:其他客户端的查询,修改,新增,删除都操作在有写锁的情况下,都要处于阻塞状态,直到其添加写锁的客户端释放写锁。
说明3:上图中的修改等应该是DML语句(Data Manipulation Language 数据操作语言,用来对数据库表中的数据进行增删改的),不是DDL语句(DDL: Data Definition Language 数据定义语言,用来定义数据库对象,数据库,表,字段)
元数据锁(meta data lock):简称 MDL
MDL加锁过程是系统自动控制,无需显示使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作,为了避免DML和DDL冲突,保证读写的正确性
在MySQL5.5 中引入了MDL, 当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)
说明1:select 和 update 分别会添加SHARED_READ 和 SHARED_WRITE, 但是SHARED_READ 和 SHARED_WRITE是兼容的,所以其他客户端是可以做更删改查的
说明2:alter table 会产生EXCLUSIVE 锁。该锁与其他MDL都互斥。ß
select object_type,object_schema,object_name,lock_type,locak_duration from performance_schema.metadata_locks;
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每一行数据是否加锁,使用意向锁来减少表锁的检查。
情况1:不加意向锁
说明1:线程A 开启一个事务,并且使用DML语句更新数据,此时会对更新的数据添加行锁,
说明2:此时线程A 的事务并没有提交事务,线程B又对该表添加一个表锁,此时添加表锁的时候,就会从第一条数据,依次检查到最后一条数据,看是否有其他的锁,如果有其他锁,需要等到其他锁释放了,才能执行添加表锁
说明3:此时添加表锁的效率就非常低
情况2:加意向锁
说明4:当线程A 开启了一个事务,并且执行了DML的更新数据的语句,此时除了会给该行数据添加行锁之外,还会添加一个基于整表的意向锁。
说明5:当线程B 再次添加表锁的时候,就不用逐行排查是否有行锁了,而是直接检查整表是否有意向锁,如果意向锁和表锁兼容则直接加表锁
说明6:如果表锁和意向锁不兼容,则仍然会等到意向锁释放了,表锁才能添加成功。
select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks;
说明1:客户端1开启事务,并且查询语句时设置一个意向共享锁(IS)
说明2:客户端2在客户端1的事务未提交的时候,创建read锁成功
说明3:意向共享锁(IS)和read共享锁兼容
说明4:执行update等DML语句时,MySQL会自动添加行锁和排他意向锁。
说明5:排他意向锁与共享read锁和排他write锁都互斥
说明6:意向锁的作用主要是解决行锁和表锁之间的矛盾。
行级锁,每次操作锁住对应的行数据,所得粒度最小,发生锁冲突的概率最低,并发度最高,应用在InnoDB存储引擎中
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁,对于行级锁,主要分为一下三类
1、行锁(record lock): 锁定单个行记录的锁,防止其他事务对此行进行update和delete,在read committed和repeatable read隔离级别下都支持。
2、间隙锁(Gap lock): 锁定索引记录间隙(不包含记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读,在repeatable read隔离级别下支持
3、临建锁(Next-Key lock): 行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙,在repeatable read隔离级别下支持
InnoDB 实现了一下两种类型的行锁
1、共享锁(S): 允许一个事物去读一行,阻止其他事务获得相同数据集的排他锁。
2、排他锁(X): 允许获取排他锁的事务更新数据,阻止其他事物获得相同数据集的共享锁和排他锁
3、不同语句的枷锁情况
4、默认情况下,InnoDB在 repeatable read隔离级别下运行,InnoDB使用next-key锁进行行搜索和索引扫描,以防止幻读
说明1:客户端1开始事务,并执行查询语句,客户端2锁的情况,显示为空表
说明2:客户端执行查询语句,并手动添加共享,在客户端2中,查询lock_mode字段中有一个S,即共享锁:
说明3:客户端2也手动开启了一个共享锁,并且成功,说明共享锁与共享锁之间是兼容的。
说明5:在客户端2上执行更新id=2的数据成功,并且查询可以看出,自动增加了一个(X)排它锁。
说明6: 因为更新id=2的数据时,id=2的这一行上没有其他的锁,所以可以执行成功
说明7:因为在客户点1上已经对id=1的行上添加了一个共享锁(S), 这是客户端2对id=1的数据做update操作时会自动在id=1的数据上在添加一个排它锁(X),这是id=1的数据上就会有共享锁(S)和排它锁(X),又因为共享锁和排它锁不兼容,所以在update id = 1 的时候就会阻塞,需要等到共享锁(S)释放了,才能执行成功。
说明8:排它锁X 与 排它锁X 也是不兼容的。
说明9:InnoDB的行锁是针对于索引加的锁,不通过索引检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁
说明10:根据业务需要,尽量使用索引当所查询条件,既快又减少阻塞。
9.1 默认情况下、InnoDB在 repeatable read 事务隔离级别运行,InnoDB使用next-key 锁进行搜索和索引扫描,以防止幻读。
9.2 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁
9.3 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁
说明1:非唯一索引,即普通索引,在做等值查询的时候,会添加三把锁
说明2:在查询的当条记录上添加一个共享锁,允许其他事务继续查询该记录
说明3:通过标注6的位置,lock_type 为S,REC_NOT_GAP 说明同时给这一行添加一个行锁
说明4:通过标注7的位置,lock_type 为 S,GAP 说明在第一个不满足查询数据的前面也会加一个间隙锁。其目的是为了防止在select 查询的时候,其他事务去往这个索引之前插入或者修改数据,这样查询就会出现幻读的现象。
9.4 索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止。
注意:间隙锁唯一的目的是防止其他事务插入间隙,间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事物在同一间隙上采用间隙锁