• java
  • go
  • 数据库
  • linux
  • 中间件
  • 书
  • 源码
  • 夕拾

  • java
  • go
  • 数据库
  • linux
  • 中间件
  • 书
  • 源码
  • 夕拾

锁与应用

目录

  • 目录
  • 全局锁
    • 场景
  • 表级锁
    • 表锁
    • 意向锁
    • 元数据锁
    • 安全地给小表加字段
  • 行锁
    • 死锁和死锁检测

全局锁

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

场景

全库得逻辑备份。

官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。一致性读是好,但前提是引擎要支持这个隔离级别。(MyISAM不支持)

既然要全库只读,为什么不使用 set global readonly=true 的方式呢?确实 readonly 方式也可以让全库进入只读状态,但我还是会建议你用 FTWRL 方式,主要有两个原因:

  • 一是,在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大,我不建议你使用。
  • 二是,在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

表级锁

MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。

表锁

表锁的语法是 lock tables … read/write。与 FTWRL 类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。

如果在某个线程 A 中执行 lock tables t1 read, t2 write;

这个语句,则其他线程写 t1、读写 t2 的语句都会被阻塞。

同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。

连写 t1 都不允许,自然也不能访问其他表。

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

意向锁

用来表事务将要请求对表中某行的x或s锁

元数据锁

MDL 不需要显式使用,在访问一个表的时候会被自动加上。MDL 的作用是,保证读写的正确性。

如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果跟表结构对不上,肯定是不行的。因此,在 MySQL 5.5 版本中引入了 MDL,当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。

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

安全地给小表加字段

  1. 存在长事务时,会一直占着MDL锁,在mysql得information_schema库得innodb_trx表查到当前执行的事务,如果刚好有长事务,那么考虑暂停ddl语句,或者kill这个事务
  2. 热点表,请求频繁时,在alter table语句里设置等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者 DBA 再通过重试命令重复这个过程。
    1
    2
    3
    ALTER TABLE tbl_name NOWAIT add column ...

    ALTER TABLE tbl_name WAIT N add column ...

行锁

innodb种,行锁在需要的时候加上,等待事务结束后释放,两阶段锁协议。

由于两阶段提交,因此如果事务中需要锁住多个行,把最可能造成锁冲突的,影响并发度的锁尽量往后放(更新语句放后边,锁住的时间就会短)。

死锁和死锁检测

出现死锁后的策略:

  1. 设置超时等待时间:innodb_lock_wait_timeout(默认为50s,通常来说这种处理方式不好)
  2. 发起死锁检测,发现死锁后,主动回滚锁链条中的某个事务,让其他事务继续执行。innodb_deadlock_detect设置为on(默认为on)

innodb将持有最少行级锁的事务回滚

官方文档翻译-innodb锁
分区表
  1. 1. 目录
  2. 2. 全局锁
    1. 2.1. 场景
  3. 3. 表级锁
    1. 3.1. 表锁
    2. 3.2. 意向锁
    3. 3.3. 元数据锁
    4. 3.4. 安全地给小表加字段
  4. 4. 行锁
    1. 4.1. 死锁和死锁检测
© 2023 haoxp
Hexo theme