概述
锁:是计算机协调多个进程或者线程并发访问某一资源的机制。在数据库中,除了传统的计算资源(CPU、RAM、IO)的争用以外。数据也是一种供多用户共享的资源。如何保证数据的并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。在MySQL中,事务的隔离级别可以由锁来模拟实现。根据锁的粒度来分,分为以下三类:
全局锁:锁定数据库中的所有表
表级锁:每次操作锁住整张表
行级锁:每次操作锁住对应的行数据
全局锁
何为全局锁?
锁的粒度最大。全局锁就是对整个数据库实例加锁,加锁后,整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞住。
我们呢,先说场景,你自己想一想是不是应该这样做,然后再去看解释:
典型的使用场景就是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
为什么要加全局锁?
分析:
假如我们不加全局锁,现在数据库中有三张表,我们想要去备份数据库的这三张表数据,但是在我们备份的过程中,我们的业务系统还在不断地向数据库中写入数据,那么此时可能会产生这样的一种情况:当我们备份表的时候,它是一个一个备份的,我们备份完库存表,然后业务系统修改了库存表的库存数据,形成了一个订单数据写入到订单表中,然后插入订单日志,此时我们备份出来的数据,将无法对应上,这就违背了数据的一致性。
如何加全局锁?
首先我们将整个数据库锁上,执行mysqldump进行数据备份,在此过程中,DML、DDL语句无法执行,但是我们可以进行查询(DQL),备份完成得到xxx.sql文件,备份完成,再进行解锁。
-- 加全局锁
flush tables with read lock;
--备份过程
mysqldump -uroot -p123456 mydb_name > mydb_name.sql;
-- 解锁
unlock tables;
虽然全局锁能够保证数据一致性,但是也会带来一些弊端。加全局锁是一个粒度比较大的操作,存在的问题有:
1.如果在主库上备份,那么在备份期间,不能执行更新,业务基本上就得停摆
2.如果在从库上备份,那么在备份期间,从库不能执行主库同步过来的二进制文件(binlog),会导致主从延迟
在InnoDB引擎中,我们可以在备份时加上--single-transaction参数来完成不加锁的一致性数据备份
命令行语句:(是bash命令行的语句,不是在mysql命令行内执行)
mysqldump --single-transaction -uroot -p123456 mydb > mydb.sql;
表级锁
表级锁的介绍
表级锁:每次操作都会锁住数据所在的整张表。锁的粒度大,发生锁冲突的概率最高,并发度最低。在MyISAM、InnoDB、BDB等存储引擎中都支持。并发度比较低。
表级锁也有细化的分类:表锁、元数据锁(MDL - meta data lock)、意向锁。
TYPE1:表锁
表共享读锁(read lock):S-共享
表独占写锁(write lock):X-排他/独占
-- 加锁
lock tables 表名... read/write;
-- 解锁
unlock tables; -- 客户端断开连接也会解锁
读锁的特点:
客户端A 将 表T 加了一个读锁,这张表就锁住了。
此时:
客户端A 可以对 表T 进行DQL读这张表的数据,但是不能进行DDL/DML等写入操作
客户端B 可以对 表T 进行DQL读这张表的数据,但是不能进行DDL/DML等写入操作
Tips:读锁共享,指的是多个客户端可以在加锁期间共享读的权力,但谁都没有写的权力
在左侧客户端进行加锁操作,然后我们在两个客户端分别执行select查询语句,我们发现都执行成功了。但是加锁客户端进行update操作时,被报错:“table was locked with a read lock and can't be updated”:表被读锁锁住了,不能被修改。而右侧客户端直接阻塞在了这里。
当左侧解锁之后,右侧被阻塞的语句才能继续执行。
写锁的特点:
客户端A 将 表T 加了一个写锁,这张表就被锁住了。
此时:
客户端A 可以对表T 进行DQL读、DML/DDL写。
客户端B 不能对表T 进行DQL读、DML/DDL写。
Tips:写锁独占,指的是加锁客户端独占表的使用权,其它客户端无法使用。
当我们加了写锁之后,加锁客户端可以执行任何操作,另外的客户端什么操作都不能执行,直接阻塞(使用ctrl+c强制结束阻塞直接退出执行)。
TYPE2:元数据锁
MDL加锁过程是系统自动控制的,无需显示的使用,在访问一张表的时候会自动加上,MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务时,不可以对元数据进行写入操作。
元数据:是指表的结构,也就是表的字段等内容。元数据锁无需我们手动操作,但是我们需要知道当我们有事务未提交时,事务DML操作的表,我们无法对其使用DDL(数据定义语言,如alter、drop、create等)。事务DDL操作的表,其它事务无法对其使用DDL、DML操作。总的来说就是避免的是DDL与DML(数据的增删改查)的冲突,保证读写的正确性。
meta data lock 在MySQL5.5中引入。当对一张表进行增删改查时,加MDL读锁(共享);当对表结构进行变更操作时,加MDL写锁(排他)。
表的第一行:我们手动加表锁时,会自动地加上 相应的 元数据锁(SHARED_READ_ONLY / SHARED_NO_READ_WRITE)
表的第二行:我们执行select、select ... lock in share mode时,会自动地加上 元数据读锁(SHARED_READ)
表的第三行:我们执行insert、update、delete、select .. for update,会自动地加上 元数据读锁(SHARED_WRITE)
表的第四行:该行对应的操作/锁,与上面三个都互斥(上面三个互相共享)。当我们执行alter table时,自动地加上了元数据写锁(EXCLUSIVE),不仅与其它共享锁互斥,排他锁之间也互相互斥。
查看元数据锁:
select object_type,object_schema,object_name,lock_type,lock_duration
from performance_schema.metadata_locks;
TYPE3:意向锁
在具体介绍意向锁之前,我们先来分析一个现象:
首先,线程A对表的一条记录上了写锁。然后,线程B想要对这个表上锁,那么能说上锁就上锁嘛,显然不可以,他会依次遍历每一个记录,看看是不是所有记录都没上锁。
嗯,乍一看,一想,确实没问题。但是当记录多的时候,再看一眼,不得了,线程B需要访问所有记录,这不合适啊,浪费时间。
为了解决这个问题,我们只需要指定一个共同的约定,谁有上锁的可能性的话,谁就创建一个意向锁,代表有锁这个表的意向,其它线程想要上锁的话,只需要检查这一个内容就可以了。这就是意向锁的设计起源。
定义:意向锁是为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
同样的,线程A操作表中某一条数据时,首先加上行锁,然后会给这个表加上一个意向锁。对于想要访问这个表的线程B,如果B想要加的锁与A的意向锁兼容,那么B就可以加上锁,否则会阻塞等到A将锁释放掉。
意向共享锁(IS):由语句select ... lock in share mode添加。
意向排他锁(IX):由insert、update、delete、select ... for update添加。
既然有分类,那么就会有兼容互斥情况。下面我们就来分析一下。
IS:与表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
IX:与表锁互斥,意向锁之间兼容。
查看意向锁以及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data
from performance_schema.data_locks;
行级锁
在上面的意向锁中,我们已经提到了行锁。行级锁,顾名思义,每次操作锁住对应的行数据。锁的粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。
MySQL的存储引擎中,MyISAM与InnoDB有三大区别:事务、外键、行级锁。
InnoDB能够确保事务完整,而前者不能
InnoDB具有外键,而前者没有
InnoDB支持行级锁,而前者不支持
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录家的锁。
对于“对索引上的索引项加锁”怎么理解呢?我们回想一下索引的基本结构,InnoDB的索引分为聚簇索引和二级索引,聚簇索引的叶子节点下面挂的是这一行的行数据,二级索引叶子节点下面挂的是对应的主键值。那么行数据是根据聚簇索引存储的,所以InnoDB的数据是基于索引组织的,并且在InnoDB中B+树的叶子结点是有序的。
对于行级锁,主要分为三类:
行锁(Record Lock):锁定单个行记录的锁,防止其它事务对此行进行update和delete。在RC,RR隔离级别下都支持。【RC-read_committed | RR-repeatable_read】
间隙锁(Gap Lock):锁定索引记录间隙(不含该条记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读,在RR隔离级别下都支持。
临键锁(Next-Key Lock):行锁和间隙锁的组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。
行锁
细分为两类:
共享锁:S-允许一个事务去读一行,阻止其它事务获得相同数据集的排他锁。
排他锁:X-允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
默认情况下,InnoDB在RR隔离级别下运行,InnoDB使用next-key锁进行搜索和索引扫描,防止幻读。
1.针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
2.InnoDB的行锁是针对索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,此时就会升级为表锁。
查看意向锁以及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data
from performance_schema.data_locks;
间隙锁/临键锁
1.索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
2.索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock退化为间隙锁。
3.索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止。
注意:间隙锁唯一目的就是防止其它事务插入间隙,间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一个间隙采用间隙锁。
感谢大家!