mysql-锁
Posted codingjav
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql-锁相关的知识,希望对你有一定的参考价值。
根据加锁的范围,mysql里面的锁大致可以分为全局锁、表级锁和行锁三类。
全局锁
全局锁是对整个数据库实例加锁。MySQL提供了一个加全局读锁的方法,命令:
Flush tables with read lock (FTWRL)
当需要让整个库处于只读状态时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
全局锁的典型使用场景是,做全库逻辑备份。也就是把整库每个表都select出来存成文本。
官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction
的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,
这个过程中数据是可以正常更新的。
你可能疑惑,有了官方自带的,为啥还要(FTWRL)呢??一致性读是好,但前提是引擎要支持这个隔离级别。对于不支持事务的引擎,如果备份中有更新,就会获取最新数据,破坏备份一致性。就需要使用FTWRL命令。
所以,single-transaction 方法只适用于所有的表使用事务引擎的库。如果有的表使用了
不支持事务的引擎,那么备份就只能通过 FTWRL 方法。这往往是 DBA 要求业务开发人员
使用 InnoDB 替代 MyISAM 的原因之一。
表级锁
MySQL里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。
表锁的语法是 lock tables … read/write。与 FTWRL 类似,可以用 unlock tables 主动
释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables 语法除了会限制别
的线程的读写外,也限定了本线程接下来的操作对象。
在还没有出现更细粒度的锁的时候,表锁是最常用的处理并发的方式。而对于 InnoDB 这
种支持行锁的引擎,一般不使用 lock tables 命令来控制并发,毕竟锁住整个表的影响面还
是太大。
另一类表级的锁是MDL(meta data lock)。MDL 不需要显式使用,在访问一个表的时候
会被自动加上。MDL 的作用是,保证读写的正确性。
在 MySQL 5.5 版本中引入了 MDL,当对一个表做增删改查操作的时候,加 MDL
读锁;当要对表做结构变更操作的时候,加 MDL 写锁。读读不互斥;读写、写写互斥;
MDL作用是防止DDL和DML并发的冲突。
事务中的 MDL 锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放。【长事务】
seasonA 开启事务没有进行关闭,模拟长事务。
对于大表DDL,大家一般都比较谨慎,而对于小表,就会比较随意。基于上面分析,讨论下如何安全地给小表加字段???
首先需要解决长事务,事务不提交,就会一直占着MDL锁。在 MySQL 的information_schema 库的 innodb_trx 表中,你可以查到当前执行中的事务。如果你要做DDL 变更的表刚好有长事务在执行,要考虑先暂停 DDL,或者 kill 掉这个长事务。
如果要变更的表是一个热点表,数据量不大但是请求很频繁,kill未必管用,比较理想的机制是,在 alter table语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者 DBA 再通过重试命令重复这个过程。
另外,尽量保证表结构变更在数据库流量低峰期操作,比如夜间,这样能更好的避免出现风险。
- 避免长事务
- 在流量低峰期进行
行锁
MySQL行锁是在引擎层由各个引擎自己实现的。并不是所有的引擎都支持行锁。不支持行锁意味着并发控制只能使用表锁,这就会影响业务并发度。
两阶段锁
如下图事务B的update语句执行时会是什么现象?id是主键
实际上事务B的update语句会被阻塞,直到事务A执行commit之后,事务B才能继续执行。
这是因为事务A持有两个记录的行锁,都是在commit的时候才释放的。也就是说,在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。那对我们有什么帮助呢??如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
死锁和死锁检测
当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会
导致这几个线程都进入无限等待的状态,称为死锁。
当出现死锁以后,两种策略:
- 一种是直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置。
- 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。
第一种策略不是很优雅,默认时长50s,对于线上服务,这个等待时间无法接受,如果时间设置过小,发生死锁确实很快可以解开,但是会出现很多误伤。所以正常情况下,还是要采用第二种策略:主动死锁检测。主动死锁检测在发生死锁的时候,是能快速发现处理的,但是有额外负担。也就是每当一个事务被锁就要检测所依赖的线程有没有被锁住;每新来被堵住的线程都要判断自己加入会不会导致死锁,这是个时间复杂度O(n)的操作,要消耗大量的CPU资源。怎么解决这种热点行更新导致的性能问题呢???
- 如果能确保业务不会出现死锁,可以临时把死锁检测关掉
- 控制并发度
- 可以考虑通过将一行改成逻辑上的多行来减少锁冲突
行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。
以上是关于mysql-锁的主要内容,如果未能解决你的问题,请参考以下文章
JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕( 可以覆盖上次的打印内)等待多个远程调用结束)(代码片段