Mysql 系列 | 锁

数据并发访问时,用锁来实现对数据访问权限的控制。

本文还是以 InnoDB 为默认存储引擎。

Mysql 中的锁

全局锁

  • 对整个数据库实例加锁

  • 全局读锁,让整个库处于只读状态

    • 加锁命令,Flush tables with read lock (FTWRL),其他线程的语句会被阻塞,数据增删改、建表修改表结构、更新类事务的提交

    • set global readonly=true 也可让数据库处于只读状态,但修改 global 变量影响很大。

      • readonly 在数据库总还用来判断是主库还是备份库

      • 客户端发生异常时,数据库一直处于只读状态,长时间不可写,影响较大;而使用 FTWRL,发生异常断开时自动释放全局锁,整个库回到正常状态。

  • 使用场景,全库逻辑备份。把每个表都 select 存成文本

    • 加了锁,如果在主库备份,不能更新数据,影响大量业务;在从库备份,不能及时把主库 binlog 更新到从库中,导致访问的数据不一致。

    • 不加锁时,备份库不是一个逻辑时间点,视图是逻辑不一致的。对于 InnoDB 默认的可重复度隔离级别,开启事务可以保证视图一致性。

    • mysqldump,导数据前开启事务实现视图一致,用 –single-transaction 参数

    • 如果引擎不支持事务,则只能用 FTWRL

表级锁

表锁

  • 加表锁,lock tables xxx read/write,会限制别的线程读写,也限制本线程接下来的操作

  • unlock tables 主动释放锁,客户端异常断开时会自动释放

元数据锁 MDL(metadata lock)

  • 访问表时,会自动加上,保证读写的正确性。

  • 进行增删改查操作时,加 MDL 读锁;修改表结构时加 MDL 写锁。

  • 读锁之间不互斥,多个线程可以同时进行增删改查。

  • 读写锁、写锁之间互斥,多个线程中,只能等其中一个提交后,另外的操作才能继续。

    • 在一个频繁读的表中,修改表结构,A、B 会话会加读锁,C 会加写锁。

    • 在 A、B 事务提交前,C 处于阻塞状态,这样后面进来的 select 都被堵塞,线程很快就会爆满。

    • 怎么安全地修改线上的表结构!

      • 修改表结构会扫描全表数据,当表数据比较多时会造成拥堵

      • 如果刚好存在长事务(information_schema 库的 innodb_trx 表中存放长事务),要先暂停表结构修改(DDL),或者 kill 掉长事务。

      • 理想状态时,在空闲的时候进行 DDL,这时可以多次重试,

      /* 当获取不到资源时,不等待直接返回错误 */
      ALTER TABLE tbl_name NOWAIT add column ...
      
      /* 等待 n 秒还没获取到资源时,返回错误 */
      ALTER TABLE tbl_name WAIT N add column ...
      

    image
    (丁奇原图)

行锁

  • InnoDB 支持行锁;MyISAM 不支持,则同一张表只能有一个更新在执行

  • 在 InnoDB 事务中,行锁是需要的时候才会加上,但并不是不需要就立刻释放,等到事务提交后才会释放。这就是两阶段锁协议

  • 因为行锁在事务提交后才会释放,如果一个事务中需要锁多行数据,则要把最可能引起锁冲突影响并发的锁放在最后

死锁

事务 A 在等待事务 B 释放 id=2 的行,事务 B 在等待事务 A 释放 id=1 的行,两个事务在互相等待对方释放行锁,都没有办法提交,就造成死锁

image
(丁奇原图)

面对死锁的对应策略,

  • 进入等待直到超时,设置超时时间(innodb_lock_wait_timeout),默认 50s

  • 死锁检测,发现死锁后主动回滚其中一个事务,默认开启(innodb_deadlock_detect)。主动死锁检测会消耗大量 CPU 资源

    • 关闭死锁检测,对业务有损

    • 控制并发量,较少每次检测数量,降低检测成本

      • 修改源码,进入引擎前排队

      • 修改表结构,分担一张表的压力


posted @ 2022-08-02 17:37  菜乌  阅读(59)  评论(0编辑  收藏  举报