概述锁是一种计算机协调多个进程或线程同时访问某个资源的机制。在数据库中,除了传统计算资源(CPU、RAM、I/O)的争夺外,数据也是众多用户共享的资源。如何保证并发访问数据的一致性和有效性是所有数据库必须解决的问题,而锁冲突也是影响数据库并发访问性能的重要因素。从这个角度来说,锁对于数据库来说尤为重要,也更加复杂。MySQL中的锁根据锁的粒度分为以下三类:全局锁:锁住数据库中的所有表。表级锁:每次操作都会锁住整个表。行级锁:每个操作锁定对应的行数据。全局锁介绍全局锁是对整个数据库实例进行加锁。加锁后,整个实例处于只读状态。后续的DML写语句、DDL语句、已经更新的事务提交语句都会被阻塞。其典型的使用场景是对整个数据库做逻辑备份,并锁定所有表以获得一致的视图,保证数据的完整性。为什么要对整个数据库的逻辑备份加锁呢?A.先分析一下不加全局锁可能出现的问题。假设数据库中有三张表:tb_stock库存表、tb_order订单表、tb_orderlog订单日志表。进行数据备份时,首先备份tb_stock存量表。然后,在业务系统中,下订单,扣除库存,生成订单(更新tb_stock表,插入tb_order表)。然后执行备份tb_order表的逻辑。在业务中插入订单日志操作。最后,备份tb_orderlog表。此时备份的数据有问题。因为备份数据,导致tb_stock表和tb_order表的数据不一致(有最新的订单信息,但是库存没有减少)。那么如何避免此类问题呢?这时候可以借助MySQL的全局锁来解决。B、我们来分析一下加了全局锁之后的情况。在对数据库进行逻辑备份之前,先给整个数据库加一个全局锁。一旦加了全局锁,其他所有的DDL和DML都被阻塞了,但是可以执行。DQL语句,即处于只读状态,数据备份是查询操作。然后,在数据逻辑备份的过程中,数据库中的数据不会发生变化,从而保证了数据的一致性和完整性。语法加globallockflushtableswithreadlock;数据备份mysqldump-uroot–p1234itcast>itcast.sqlreleaselockunlocktables;给特征库加全局锁是一个比较重的操作,有以下问题:如果主库备份,那么备份期间无法进行更新,业务基本要关掉。如果备份在从库上,从库在备份期间无法执行从主库同步过来的二进制日志(binlog),会造成主从延迟。在InnoDB引擎中,我们可以在备份的时候加上参数--single-transaction参数来完成不加锁的一致性数据备份。mysqldump--single-transaction-uroot–p123456itcast>itcast.sqltable-levellock引入表级锁,每次操作锁定整个表。加锁粒度大,锁冲突概率最高,并发度最低。用于MyISAM、InnoDB、BDB等存储引擎。表级锁主要分为以下三类:表锁元数据锁(metadatalocks,MDL)意向锁表锁,表锁分为两大类:表共享读锁(readlocks)表独占写锁(writelock)语法:lock:locktables表名...读/写。释放锁:解锁表/客户端断开连接。特点:A.读锁左边是client1,对指定表加读锁,不会影响右边client2的读,但是会阻塞右边client的写。测试:B、写锁左边是client1,给指定表加写锁,会阻塞右边client的读写。测试:结论读锁不会阻塞其他客户端的读,但是会阻塞写。写锁将阻止其他客户端的读取和其他客户端的写入。元数据锁元数据锁,元数据锁,简称MDL。MDL锁定过程由系统自动控制,不需要显式使用。它会在访问表时自动添加。MDL锁的主要作用是维护表元数据的数据一致性。当表上有活动事务时,无法写入元数据。为了避免DML和DDL的冲突,保证读写的正确性。这里的元数据可以简单理解为一个表的表结构。也就是说,当某个表涉及未提交的事务时,不能修改该表的表结构。MDL是在MySQL5.5中引入的。对表进行增删改查时,添加MDL读锁(共享);改变表结构时,添加MDL写锁(独占)。普通SQL操作时加的元数据锁:对应SQL锁类型描述locktablesxxxread/writeSHARED_READ_ONLY/SHARED_NO_READ_WRITEselect,select...lockinsharemodeSHARED_READ兼容SHARED_READ和SHARED_WRITE,与EXCLUSIVEinsert,update,delete互斥,select...forupdateSHARED_WRITE兼容SHARED_READ,SHARED_WRITE,与EXCLUSIVE互斥altertable...EXCLUSIVE与其他MDL互斥演示:执行SELECT,INSERT,UPDATE,DELETE等语句时,添加元素数据共享锁(SHARED_READ/SHARED_WRITE)兼容。SELECT语句执行时,加上元数据共享锁(SHARED_READ),会阻塞元数据排他锁(EXCLUSIVE),是互斥的。我们可以通过如下SQL查看数据库中的元数据锁:selectobject_type,object_schema,object_name,lock_type,lock_durationfromperformance_schema.metadata_locks;我们可以通过上面的SQL语句在操作过程中查看元数据Locking锁的状态。mysql>从performance_schema.metadata_locks选择object_type、object_schema、object_name、lock_type、lock_duration;+------------+------------------+----------------+------------+--------------+|对象类型|对象架构|对象名称|锁类型|lock_duration|+------------+--------------------+----------------+----------------+----------------+|表|MySQL_高级|tb_user|分享阅读|交易||表|MySQL_高级|tb_user|分享阅读|交易||表|MySQL_高级|tb_user|共享_写入|交易||表|MySQL_高级|用户日志|共享_写入|交易||表|性能模式|元数据锁|分享阅读|交易|+------------+--------------------+----------------+------------+----------------+5rowsinset(0.00sec)mysql>altertabletb_useraddcolumnjavaint;...堵塞--另开一个客户端窗口mysql>selectobject_type,object_schema,object_name,lock_type,lock_durationfromperformance_schema.metadata_locks;+------------+-------------------+------------------------+------------------+----------------+|对象类型|对象架构|对象名称|锁类型|lock_duration|+------------+--------------------+----------------------+--------------------+--------------+|表|MySQL_高级|tb_user|分享阅读|交易||全球|空|空|INTENTION_EXCLUSIVE|声明||备用锁|空|空|INTENTION_EXCLUSIVE|交易||图式|MySQL_高级|空|INTENTION_EXCLUSIVE|交易||表|MySQL_高级|tb_user|共享_可升级|交易||表空间|空|MySQL_Advanced/tb_user|INTENTION_EXCLUSIVE|交易||触发|MySQL_高级|tb_user_insert_trigger|独家|交易||触发|MySQL_高级|tb_user_update_trigger|独家|交易||触发|MySQL_高级|tb_user_delete_trigger|独家|交易||表|MySQL_高级|#sql-261d_18|独家|声明||表|MySQL_高级|tb_user|独家|交易||----------+------------------------+--------------------+------------+12rowsinset(0.00sec)意向锁介绍为了避免DML执行时添加的行锁和表的冲突锁,InnoDB中引入了意向锁,使得表锁不需要检查每一行数据是否被锁定,意向锁用于减少对表锁的检查。如果没有意向锁,客户端给表加行锁后,客户端如何在第二个终端给表加表锁,我们通过示意图简单分析一下:首先,客户端一个启动一个事务,然后执行DML操作。在执行DML语句时,会对涉及到的行加行锁。当client2要给这张表加表锁时,会检查当前表是否有对应的行锁。如果没有,加一个表锁。这时,它会从第一行数据检查到最后一行数据。效率较低。有了意向锁后:Client1在进行DML操作时,会对涉及到的行加行锁,同时也会对表加意向锁。而其他客户端在给这张表加表锁时,会根据加在表上的意向锁来判断表锁是否加成功,而不是逐行判断行锁情况。分类意向共享锁(IS):由语句select...添加的共享模式的锁。兼容tablelock共享锁(read),兼容tablelockexclusivelock(write)。意向排他锁(九):通过insert,update,delete,select...forupdate添加。共享锁(读)和排他锁(写)与表锁互斥,意向锁不互斥。一旦事务提交,意向共享锁和意向排他锁将自动释放。可以使用如下SQL查看意向锁和行锁的锁定状态:selectobject_schema,object_name,index_name,lock_type,lock_mode,lock_datafromperformance_schema.data_locks;论证:A.意向共享锁兼容表读锁B.意向排他锁、表读锁、写锁是互斥的行级锁。引入行级锁,每个操作锁定对应的行数据。加锁粒度最小,锁冲突概率最低,并发度最高。应用于InnoDB存储引擎。InnoDB的数据是根据索引来组织的,行锁是通过在索引上锁定索引项来实现的,而不是锁定记录。对于行级锁,主要分为以下三类:RecordLock:锁定单行记录,防止其他事务更新和删除该行的锁。它在RC和RR隔离级别下均受支持。GapLock:锁定索引记录间隙(不包括这条记录),保证索引记录间隙不变,防止其他事务插入这个间隙,造成幻读。它在RR隔离级别下受支持。Next-KeyLock:rowlock和gaplock的组合,在锁住数据的同时,锁住数据前面的Gap。在RR隔离级别下支持。行锁介绍InnoDB实现了以下两种行锁:共享锁(S):允许一个事务读取一行,防止其他事务获得对同一数据集的独占锁。排他锁(X):允许获取排他锁的事务更新数据,防止其他事务获取同一数据集的共享锁和排他锁。两种行锁的兼容性如下:普通SQL语句,执行时,加行锁如下:SQL行锁类型说明INSERT...独占锁自动加锁UPDATE...独占锁自动加锁DELETE。..排他锁自动加锁SELECT(normal)不带任何锁SELECT...LOCKINSHAREMODE共享锁需要在SELECT后手动加LOCKINSHAREMODESELECT...FORUPDATE排他锁需要在SELECTFOR后手动加UPDATEDemo默认情况下,InnoDB运行在REPEATABLEREAD事务隔离级别,InnoDB使用next-key锁进行搜索和索引扫描以防止幻读。当针对唯一索引进行搜索时,对已有记录进行等价匹配时,会自动优化为行锁。InnoDB的行锁是针对索引的锁。如果不通过索引条件检索数据,InnoDB会锁住表中的所有记录,然后升级为表锁。可以使用如下SQL查看意向锁和行锁的锁定状态:selectobject_schema,object_name,index_name,lock_type,lock_mode,lock_datafromperformance_schema.data_locks;示例演示数据准备:CREATETABLE`stu`(`id`intNOTNULLPRIMARYKEYAUTO_INCREMENT,`name`varchar(255)DEFAULTNULL,`age`intNOTNULL)ENGINE=InnoDBCHARACTERSET=utf8mb4;INSERTINTO`stu`VALUES(1,'tom',1);INSERTINTO`stu`VALUES(3,'cat',3);INSERTINTO`stu`VALUES(8,'rose',8);INSERTINTO`stu`VALUES(11,'jetty',11);INSERTINTO`stu`VALUES(19,'lily',19);INSERTINTO`stu`VALUES(25,'luci',25);在演示行锁时,我们将通过上表进行演示。A.普通的select语句执行时不会加锁。B.select...lockinsharemode,添加共享锁,共享锁和共享锁兼容。共享锁和排它锁之间的互斥。客户端1获取id为1的行的共享锁,客户端2可以获取id为3的行的排它锁,因为不是同一行数据。而客户端2如果要获取id为1的行的排他锁,就会处于阻塞状态,认为共享锁和排他锁是互斥的。C.排他锁与独占锁的互斥当客户端1执行update语句时,会对id为1的记录加排他锁;client2,如果它也执行update语句更新id为1的数据,同样给id为1的数据加上排他锁,但是client2会被阻塞,因为排他锁是互斥的。直到客户端一提交事务后,该行的行锁才会被释放,此时客户端二解除阻塞。D.没有索引行锁升级为表锁。stu表中的数据如下:mysql>select*fromstu;+----+-----+--------+|编号|年龄|姓名|+----+-----+------+|1|1|Java||3|3|Java||8|8|玫瑰||11|11|码头||19|19|百合||25|25|luci|+----+-----+--------+6rowsinset(0.00sec)在两个客户端中执行以下操作:在客户端一中,启动事务并执行更新语句更新名称为Lily的数据,即id为19的记录。然后在客户端2更新id为3的记录,但不能直接执行,会处于阻塞状态。为什么?原因是因为此时client1根据name字段更新时,name字段没有索引。如果没有索引,此时行锁会升级为表锁(因为行锁是加在索引项上的锁,名字没有索引)。接下来,我们为名称字段创建一个索引。索引建立好后,我们再做一个测试:此时我们可以看到客户端1启动了事务,然后根据名称进行更新。另一方面,客户端2,在更新id为3的数据时,更新成功,没有进入阻塞状态。由此可见,我们可以通过基于索引字段进行更新操作来避免行锁升级为表锁的情况。GapLocks&AdjacentKeyLocks默认情况下,InnoDB运行在REPEATABLEREAD事务隔离级别,InnoDB使用next-key锁进行搜索和索引扫描以防止幻读。对索引(唯一索引)的等价查询,当锁定一条不存在的记录时,优化为间隙锁。在一个索引(非唯一普通索引)的等价查询中,当向右遍历时最后一个值不满足查询要求时,next-key锁退化为间隙锁。索引(唯一索引)上的范围查询——将访问第一个不满足条件的值。注意:间隙锁的唯一目的是防止其他事务插入间隙。间隙锁可以共存,并且一个事务获取的间隙锁不会阻止另一个事务在同一间隙上获取间隙锁。实例演示A.对索引(唯一索引)的等价查询,当锁定一条不存在的记录时,优化为间隙锁。B、对索引(非唯一普通索引)的等价查询,当向右遍历时最后一个值不满足查询要求时,next-key锁退化为间隙锁。介绍与分析:我们知道InnoDB的B+树索引,叶子节点是有序的双向链表。假设我们要根据这个二级索引查询值为18的数据,加上共享锁,是否可以只锁18行呢?不会,因为是非唯一索引,这个结构中可能有多个18,所以加锁的时候会继续向后查找,找到一个不满足条件的值(当前情况下是29).这个时候会加keylock到18,锁住29之前的空隙。C.索引上的范围查询(唯一索引)——会取到第一个不满足条件的值。查询条件为id>=19,加共享锁。这时候我们可以根据数据库表中已有的数据将数据分成三部分:[19](19,25](25,+∞]所以在数据库数据加锁的时候,加上行锁19、25Pro键锁(包括25和25之前的间隙),正无穷键锁(正无穷和之前的间隙)。本文由传智教育博学谷荒野建筑师教研组发表。如本文对你有帮助有帮助,欢迎关注点赞;有什么建议也可以留言或私信,你的支持是我坚持创作的动力,转载请注明出处!
