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 ...
-
(丁奇原图) -
行锁
-
InnoDB 支持行锁;MyISAM 不支持,则同一张表只能有一个更新在执行
-
在 InnoDB 事务中,行锁是需要的时候才会加上,但并不是不需要就立刻释放,等到事务提交后才会释放。这就是两阶段锁协议
-
因为行锁在事务提交后才会释放,如果一个事务中需要锁多行数据,则要把最可能引起锁冲突影响并发的锁放在最后
死锁
事务 A 在等待事务 B 释放 id=2 的行,事务 B 在等待事务 A 释放 id=1 的行,两个事务在互相等待对方释放行锁,都没有办法提交,就造成死锁。
(丁奇原图)
面对死锁的对应策略,
-
进入等待直到超时,设置超时时间(innodb_lock_wait_timeout),默认 50s
-
死锁检测,发现死锁后主动回滚其中一个事务,默认开启(innodb_deadlock_detect)。主动死锁检测会消耗大量 CPU 资源
-
关闭死锁检测,对业务有损
-
控制并发量,较少每次检测数量,降低检测成本
-
修改源码,进入引擎前排队
-
修改表结构,分担一张表的压力
-
-