MySql 锁设计是为了处理并发问题,作为多用户共享资源,当出现并发访问的时候,数据库要合理的控制资源访问规则。而锁就是用来实现这些访问规则的。
MySql 锁设计是为了处理并发问题,作为多用户共享资源,当出现并发访问的时候,数据库要合理的控制资源访问规则。而锁就是用来实现这些访问规则的。
全局锁
含义:对整个数据库实例进行加锁。
方式:MySQL 提供了一个加全局读锁的方法,命令是 Flush table with read lock(FTWRL)
,使用该命令,之后的以下线程语句都会被阻塞:
- 数据更新(数据增删改)
- 数据定义(建表、改表结构)
- 更新事务提交语句
场景:做全库备份。也就是把整个表 select 出来存成文本。通过 FTWRL 确保不会有其他线程对数据库做更新,整个库成为只读状态。
注意:
- 在主库备份,备份期间不能执行更新。业务基本停摆。
- 从库备份,期间无法与主库同步过来 binlog,会导致主从延迟。
其他方式:可以采用事务隔离,事务隔离级别的可重复读,在事务开始的时候,可以拿到一致性的视图,接下来所读的数据都是一样的,由于 MVCC 支持,过程中数据可以正常更新。
MySQL 官方自带的逻辑备份工具是 mysqldump,使用 single-transaction
参数,导数据前会启动一个事务,确保拿到一致性视图,而且由于 MVCC 支持,过程中数据可以正常更新。
前提是数据库引擎支持这个隔离级别。
如果设置全库只读?
命令 set global readonly=true
,可以进入全库只读,但是不建议,不如用 FTWRL,原有:
- readonly 可以被用来做其他逻辑,比如用来判断一个库是主库还是从库
- 如果客户端突然端口,如果是 FTWRL ,MySQL 会自动释放全局锁,整个库回到正常更新状态。如果是 readonly,客户端发送一次,数据库则保持 readonly,导致整个库长时间不可写,风险高。
表级锁
种类
- 表锁
- 元数据锁(metadata lock,MDL)
表锁
语法:lock table ... read/write
,和 FTWRL 类似,unlock tables 可以用来主动释放锁,可以在客户端断开时自动释放。但是限制了本线程接下来的操作:
- 对于线程 A 中执行 lock tables t1 read、t2 write。其他线程写 t1 、读写 t2 会被阻塞,同时线程 A 执行 unlock tables 之前,也只能执行读 t1、读写 t2 操作,连写 t1 也不写,也不能来访问其他表。
但是表锁锁住整个表,影响范围大,一般不适用 lock tables 命令来控制并发。
MDL(metadata lock)
在 MySQL 5.5 版本中引入 MDL,不需要显示使用,在访问一个表的时候,会自动加上。
作用:保证读写正确性。
- 当对一个表做增删改查,加 MDL 读锁
- 对表结构操作,加 MDL 写锁。
- 读锁之间不互斥,多个线程可以同时对一张表增删改查。
- 读写锁、写锁之间互斥。保证变更表结构的安全性,如果有两个线程同时对一个表加字段,其中一个要等另一个执行完毕才开始。
注意:MDL 会直到事务提交才释放,表结构改变的时候,要小心避免锁住线上的查询和更新。
行锁
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
建议:
如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
死锁和死锁检测
当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。
解决死锁的策略
- 直接进入等等,直到超时,超时时间可以通过
innodb_lock_wait_timeout
设置。 - 发起死锁检查,发现死锁后主动回滚死锁链中某一个事务。让其他事务继续执行,将参数
innodb_deadlock_detect
设置为 on。
两个策略缺点:
innodb_lock_wait_timeout
默认为 50 s,意味者如果采用的话,第一个锁住的线程 50 s 后才会超时退出。其他线程继续。对于在线服务难以接受。但是如果时间太短,又很容易误伤- 死锁检查需要一定的负担,每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。
如何解决热点行更新导致的性能问题?
- 如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关闭掉。一般不建议采用
- 控制并发度,对应相同行的更新,在进入引擎之前排队。这样在InnoDB内部就不会有大量的死锁检测工作了。
- 将热更新的行数据拆分成逻辑上的多行来减少锁冲突,但是业务复杂度可能会大大提高。比如账户余额,分为多行数据,需要的时候求总数即可,但是插入的时候代码的逻辑要稍稍改变。
注意:innodb行级锁是通过锁索引记录实现的,如果更新的列没建索引是会锁住整个表的。