根据加锁的范围,Mysql里面的锁大致可以分成全局锁、表级锁和行锁
全局锁
-
Flush tables with read lock。当你需要整个库处于只读状态的时候,可以使用这个命令,之后其他线程以下语句会被阻塞;数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
-
全局锁的典型使用场景,做全库逻辑备份:官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
-
一致性读是好,但前提是引擎要支持这个隔离级别,single-transaction方法只适用于所有表使用事务引擎的库,set global readonly=true的方式也会让库进入只读状态
-
有些系统中,readonly的值会被用来做其他逻辑,比如判断库是主库还是从库
-
异常机制下有差异,如果执行FTWRL命令之后由于客户端发生异常断开,那么Mysql会自动释放这个全局锁,整个库回到正常更新状态。而将整个库设置为readonlu之后,由于客户端发生异常,则数据库一直保持readonlu状态,这样导致整个库长时间处于不可写状态
-
业务的更新不只是增删改数据(DML),还有可能是加字段等修改表结构的操作(DDL)。
表级锁
Mysql表级锁分为两种:一种表锁,一种元数据锁
-
表锁的语法是lock tables ...read/write。lock tables语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
-
还没有更细粒度的锁的时候,表锁是最常用处理并发的方式,而对于Innodb这种行锁引擎,一般不使用lock tables命令来控制并发,毕竟锁住整个表的影响面还是太大
-
另一种表级锁是MDL,MDL不需要显示使用,在访问一个表的时候,会自动加上。MDL的作用保证读写正确性,增删改查的时候MDL加读锁,当要对标结构变更的时候,加MDL写锁
- 读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。
- 读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
-
如果某个表查询语句频繁,而且客户端重试机制,也就是说超时会再起一个新的session请求的话,这个库线程很快就会满,事务MDL锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而是等整个事务提交后释放
-
一个变更热点表,虽然数据量不大,但是上面请求很频繁,而你不得不加个字段
-
alter tabel设定等待时间,如果在这个时间里面拿到MDL写锁最好,拿不到阻塞后面的业务语句,先放弃。之后开发人员或者DBA重复这个过程
-
MDL会直到事务提交才提交释放,在表结构变更的时候,你一定要小心不要导致锁住线上查询和更新
-
Online DDL过程是这样
- 拿MDL写锁
- 降级MDL读锁
- 真正做DDL
- 升级MDL写锁
- 释放MDL锁
行锁
- Innodb支持行锁
- 行锁就是针对数据表行记录的锁,比如事务A更新一行,而这时候事务B也要更新同一行,必须等A更新完B才能更新
从两阶段锁说起
- 在Innodb事务,行锁是需要的时候加上,但不是不需要了立刻释放,而是等到事务结束时才释放,这个就是两阶段锁协议
死锁与死锁检测
- 当并发系统不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源,就会导致这几个线程进入无线等待的状态--死锁
- 第一个策略出现死锁后,超过50s才能超时退出,但是超时时间设置太短,会出现误伤
- 主动死锁检测,而且innodb_deadlock_detect的默认值发现并进行处理,但也是有额外负担
- 假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。
怎么解决热点行更新导致性能问题?
- 一种头痛医头的方法,就是如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉
- 另一种思路是控制并发度,根据上面的分析,同一行时只有10个线程更新,那么死锁的检测成本很低,就不会出现这种问题,减少死锁的主要方向,就是控制相同资源的并发事务量