2022-10-27 433
无论什么时候,只要存在多个连接在同一时刻修改数据,都会涉及到并发控制的问题。MySQL实现了两个层面的并发控制:服务层和引擎层。
共享锁:共享锁(shared lock)也称为读锁(read lock)。共享锁是共享的,或者说是相互不阻塞的。多个连接在同一时刻可以同时读取同一个资源,而不相互干扰。
排他锁:排他锁(exclusive lock)也称为写锁(write lock)。写锁是排他的,也就是一个写锁会阻塞其他的写锁和读锁。
悲观锁:对数据被外界修改保持悲观的态度,在整个数据处理过程中,数据都处于锁定状态。
乐观锁:它认为数据一般情况下不会造成冲突,在数据更新的时候才会对数据的冲突与否进行校验。
全局锁:对整个数据库实例加锁,它将整个数据库实例处于只读的状态。
表级锁:对整个表进行加锁的方式。MySQL表级锁分为表锁和元数据锁。
行级锁:行锁可以最大程度的支持并发处理,行锁是存储引擎层实现的,而MySQL服务层并没有实现行锁。InnoDB存储引擎行级锁类型:Record Lock、Gap Lock、Next-key Lock。
共享锁:共享锁(shared lock)也称为读锁(read lock)。共享锁是共享的,或者说是相互不阻塞的。多个连接在同一时刻可以同时读取同一个资源,而不相互干扰。
select......lockinsharemode;
测试时,设置事务手动提交:set autocommit = 0,后续如果没有明确的提示,autocommit都是0。
测试时,大家开启两个窗口,建立两个连接,窗口1和窗口2分别对应事务A和事务B。
如果写锁等待时间过长,则会超时退出。
窗口1 mysql>updateusersetage=20whereid=6; ERROR1205(HY000):Lockwaittimeoutexceeded;tryrestartingtransaction
如果在窗口1中的事务在执行写操作等待期间,窗口2的事务也执行同一行数据的写操作,则会导致死锁错误。
窗口2 mysql>updateusersetage=30whereid=6; ERROR1213(40001):Deadlockfoundwhentryingtogetlock;tryrestartingtransaction
排他锁(exclusive lock)也称为写锁(write lock)。写锁是排他的,也就是一个写锁会阻塞其他的写锁和读锁。
select......forupdate;
不论是乐观锁还是悲观锁都是人们定义的一种概念,并不是一种锁实现,它是一种思想。乐观锁比较适用于读多写少的场景,悲观锁适用于写多读少的场景。
当我们对数据库的某一条数据进行修改操作时,为了避免同时有其他人对同一行数据进行修改,通过对数据进行加锁的方式以防止并发问题。这种借助了数据库的锁机制,在修改数据之前先锁定再修改的方式称为悲观锁(Pessimistic Lock)。
悲观锁具有强烈的独占性和排他性,在整个数据的写操作过程,都将数据处于锁定状态。悲观锁的实现往往需要数据库提供的锁机制。
悲观锁的实现:
乐观锁是相对于悲观锁的概念,乐观锁是假设数据一般情况下都不会存在并发冲突,在数据进行更新的时候才会对数据的冲突与否进行验证。如果存在冲突,则告诉用户结果,由用户决定下一步该怎么做。
乐观锁是一种宽松的加锁方式,它不需要使用数据库本身的锁机制。
乐观锁的实现:
全局锁:对整个数据库实例加锁,它将整个数据库实例处于只读的状态。
Flushtableswithreadlock;
全局锁常用来对整个数据库实例进行逻辑备份。
全局锁加锁期间,业务的数据更新操作(DML)和 表结构的修改操作(DDL)都是会被锁住的。
此时你是不是有个疑问:开发中备份都是直接使用mysqldump,什么时候使用FTWRL呢?
官方自带的逻辑备份工具mysqldump 使用参数-single -transaction的时候,在导出数据的时候就会启动一个事务,来确保拿到一致性视图,在学习事务隔离的时候我们了解到,基于MVCC的一致性视图,这个过程中的数据是可以正常更新的。但是我们要知道事务是引擎层的实现,并不是每个存储引擎都支持事务。我们在开发中大部分的备份使用的是mysqldump,主要是因为我们的存储引擎大部分情况都是使用的默认的引擎InnoDB。
表级锁:即是对整张表进行加锁。表的定义包含两个部分:数据和结构,所以表级锁也分为两类:表锁和元空间锁。
表锁是MySQL中最基本的锁策略,并且也是开销最小的策略。表锁会锁住整张表,在对表进行写操作(插入、删除、更新等)前,需要先获取写锁,它会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他读取的用户才能获取读锁。读锁之前互相不会造成阻塞。
写锁的优先级高于读锁,因此一个写锁的请求可能会被插入到读锁队列前面,但是读锁是不能插入到写锁的前面的。
--对表加读锁 locktables......read; --对表加写锁 locktables......write; --释放锁 unlocktables;
MySQL5.5版本中引入了元空间锁(matadata lock),当对一个表做增删改查操作的时候,会添加MDL读锁。当对表结构变更操作的时候,会添加MDL写锁。MDL的作用是防止DDL和DML并发的冲突。
MDL锁是系统默认添加的,不需要显式的添加。
MySQL的行锁是在引擎层由各个存储引擎实现的。并不是所有的存储引擎都支持行锁,比如MyISAM引擎是不支持行锁的。不支持行锁意味着并发控制的时候只能使用表锁,这也意味着同一个时刻同一个表只有一个更新执行,严重影响了并发。InnoDB是支持行锁的,这也是InnoDB能替代MyISM的重要原因之一。
在InnoDB事务,行锁是需要的时候加上的,但并不是不需要了就立刻释放的,而是需要等到事务结束后才会释放锁。这个就是两阶段锁协议。
InnoDB是采用的两阶段锁协议。在事务执行的过程中,随时都可以锁定,锁只有在执行commit或者rollback的时候才会释放,并且所有的锁是在同一个时刻释放的。
事务A执行了两条update语句之后,事务B也执行update语句,但是事务B阻塞直到事务A提交事务。
我们创建一张简单表t,其中id为主键,a为索引,插入6条数据。
CREATETABLE`t1`( `id`int(11)NOTNULL, `a`int(11)DEFAULTNULL, `b`int(11)DEFAULTNULL, PRIMARYKEY(`id`), KEY`idx`(`a`) )ENGINE=InnoDBDEFAULTCHARSET=utf8; insertintot1values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);
对于表t1,6条数据就产生了7个间隙,如下图所示:
我们看前面学习的写锁的例子:
begin; select*fromt1whereb=5forupdate; commit;
间隙锁之间并不会冲突,与间隙锁存在冲突关系的是往这个间隙中插入数据的操作。
行锁和间隙锁的合称为next-key lock每个next-key lock都是一个前开后闭的区间。这里不要混淆了,间隙锁是一个开区间,间隙锁加上行锁生成的next-key lock是一个前开后闭的区间。
间隙锁和next-key lock的引入帮助我们解决了幻读的问题。
间隙锁是可重复隔离级别下才生效的,如果我们把隔离级别设置为读提交,那就没有间隙锁了。
前面我们在学习其他锁的时候,为了省事把autocommit设置为了0,现在我们需要把autocommit设置为1;
锁按照锁粒度分为:全局锁、表锁和行锁。
行锁是引擎层的实现,我们文中描述的行锁都是基于InnoDB存储引擎的实现。
InnoDB的行锁采用的两阶段锁协议,锁在需要的时候添加,只有在事务commit或者rollback时才会一次性释放所有锁。
可重复度隔离级别下存在间隙锁,如果设置为其他的隔离级别下就没有间隙锁了。间隙锁即是对行数据的两边间隙进行加锁,间隙锁加上行锁合称为next-key lock,它是一个前开后闭的区间。间隙锁解决了幻读的问题。
大家在学习锁的时候还需多动手实践。
原文链接:https://77isp.com/post/10352.html
=========================================
https://77isp.com/ 为 “云服务器技术网” 唯一官方服务平台,请勿相信其他任何渠道。
数据库技术 2022-03-28
网站技术 2022-11-26
网站技术 2023-01-07
网站技术 2022-11-17
Windows相关 2022-02-23
网站技术 2023-01-14
Windows相关 2022-02-16
Windows相关 2022-02-16
Linux相关 2022-02-27
数据库技术 2022-02-20
抠敌 2023年10月23日
嚼餐 2023年10月23日
男忌 2023年10月22日
瓮仆 2023年10月22日
簿偌 2023年10月22日
扫码二维码
获取最新动态