MySql全局锁,表级锁和行锁

MySql

  • 全局锁
  • 表级锁
  • 行锁
  • 死锁和死锁检测

全局锁

顾名思义,全局锁就是对整个数据库实例加锁。MySQL提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命 令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括 建表、修改表结构等)和更新类事务的提交语句。

全局局锁的的典型使用场景是,做全库逻辑备份。。也就是把整库每个表都select出来存成文本。

FTWRL 前有读写的话 ,FTWRL 都会等待 读写执行完毕后才执行
FTWRL 执行的时候要刷脏页的数据到磁盘,因为要保持数据的一致性 ,理解的执行FTWRL时候是 所有事务 都提交完毕的时候

解除全局锁的方法是:1. 断开执行全局锁的session;2. unlock tables;

全局锁的典型使用场景是,做全库逻辑备份。也就是把整库每个表都 select 出来存成文本。

但是让整库都只读,听上去就很危险:

  • 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
  • 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog,会导致主从延迟。

既然要全库只读,为什么不使用 set global readonly=true 的方式呢?

  • 一是,在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大,我不建议你使用。

  • 二是,在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

表级锁

表锁的语法是 lock tables。。。read/write,与 FTWRL 类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。

在还没有出现更细粒度的锁的时候,表锁是最常用的处理并发的方式,而对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并发,毕竟锁住整个表的影响面还是很大。

另一种表级的锁是 MDL,MDL 不需要显示使用,在访问一个表的时候就会被自动加上。MDL 的作用是,保证读写的正确性。你可以想象一下,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果跟表结构对应不上,肯定是不行的。

因此在 MySQL 5.5 之后加入了MDL,党对一个表做增删改查操作的时候,加 MDL 读锁,当对表做结构变更操作的时候,加了 MDL 写锁。

  • 读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查

  • 读写之间、写写之间是互斥的,用来保证变更结构操作的安全性。因此如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。

行锁

MySQL的行锁是在引擎层由各个引擎自己实现的。

但并不是所有的引擎都支持行锁,比如 MyISAM引擎就不支持行锁。不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同 一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。InnoDB是支持行锁的, 这也是MyISAM被InnoDB替代的重要原因之一。

行锁就是针对数据表中行记录的锁。这很好理解,比如事务A更新了一行,而这时候 事务B也要更新同一行,则必须等事务A的操作完成后才能进行更新。

死锁和死锁检测

当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致 这几个线程都进入无限等待的状态,称为死锁。这里用数据库中的行锁举个例子
MySql全局锁,表级锁和行锁_第1张图片
这时候,事务A在等待事务B释放id=2的行锁,而事务B在等待事务A释放id=1的行锁。 事务A和 事务B在互相等待对方的资源释放,就是进入了死锁状态。

当出现死锁以后,有两种策略:
一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout来设置。
另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事 务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑。 在InnoDB中,innodb_lock_wait_timeout的默认值是50s,意味着如果采用第一个策略,当出现 死锁以后,第一个被锁住的线程要过50s才会超时退出,然后其他线程才有可能继续执行。对于 在线服务来说,这个等待时间往往是无法接受的。

但是,我们又不可能直接把这个时间设置成一个很小的值,比如1s。这样当出现死锁的时候,确 实很快就可以解开,但如果不是死锁,而是简单的锁等待呢?所以,超时时间设置太短的话,会 出现很多误伤。 所以,正常情况下我们还是要采用第二种策略,即:主动死锁检测,而且 innodb_deadlock_detect的默认值本身就是on。主动死锁检测在发生死锁的时候,是能够快速发 现并进行处理的,但是它也是有额外负担的。

解决办法

  1. 最简单的方法,直接关闭死锁检测,如果业务中没有这种死锁情况,可以将其关闭,但是这种操作本身带有一定的风险,因为业务设计的时候一般不会把死锁当做一个严 重错误,毕竟出现死锁了,就回滚,然后通过业务重试一般就没问题了,这是业务无损的。而关 掉死锁检测意味着可能会出现大量的超时,这是业务有损的

  2. 你会发现如果并发能够控制住,比如同一行同时 最多只有10个线程在更新,那么死锁检测的成本很低,就不会出现这个问题。一个直接的想法 就是,在客户端做并发控制。但是,你会很快发现这个方法不太可行,因为客户端很多。我见过 一个应用,有600个客户端,这样即使每个客户端控制到只有5个并发线程,汇总到数据库服务 端以后,峰值并发数也可能要达到3000。

你可能感兴趣的:(MySql全局锁,表级锁和行锁)