全局锁
对整个数据库实例加锁。命令是:Flush tables with read lock
。使用这个命令后,数据库编程只读。全局锁的典型使用场景是做全库的逻辑备份。
将整个库锁定有很大缺点:
- 如果在主库上备份,备份期间所有更新不能执行,业务基本停摆
- 如果在从库上备份,备份期间不能从主库同步数据,造成主从延迟
官方的逻辑备份工具是mysqldump,使用-single-transaction参数可以在备份前启动一个事务,拿到一致性视图。由于MVCC的支持,这个过程中数据可以正常更新。
但是single-transaction模式只适用于所有的表都使用了支持事务的引擎(比如InnoDB),否则可能会造成数据不一致。MyISAM就不支持事务。
set global readonly=true
也可以将全库设置为只读,备份数据还是建议使用FTWRL方式,因为:
- 有些系统中,readonly=true会被用来做其他逻辑判断,比如判断一个库是主库还是从库,这样修改readonly可能会造成意外影响。
- FTWRL模式下如果客户端发生异常断开,整个库会自动恢复正常状态,而readonly不会。
表级锁
表级锁有两种:一种是表锁,一种是元数据锁(MDL)。
在没有出现更细粒度的锁的时候,表锁是最常用的处理并发的方式。而对于InnoDB这种支持行锁的引擎,一般不使用lock tables命令来控制并发。
MDL不需要显示使用,在访问一个表的时候会自动加上。当对一个表做增删改查操作的时候,加MDL读锁;在修改表结构的时候,加MDL写锁。
给表加字段的时候要注意:MySQL的读写锁是队列形式的,如果一个写锁在等待,后边的读锁也加不上,会造成该表的读写都不可用。
行锁是存储引擎自己实现的,并不是所有引擎都支持行锁。
两阶段锁
在InnoDB事务中,行锁是需要的时候才加上的,但是并不是不需要了就立即释放,而是等到事务结束才释放。这个就是二阶段锁协议。
最佳实践
如果事务中要锁多个行,要把最可能造成冲突的锁尽量往后放。文中举了一个买电影票的例子,要把更新影院的数据放在最后。
死锁和死锁检测
例子:
事务A要更改行1和2,事务B要更改行2和1。事务A更改完行1的时候恰好事务B更改了行2。这样,事务A持有行1的行锁,请求行2的行锁;事务B持有行2的行锁,请求行1的行锁,造成死锁。
死锁解决办法:
- InnoDB有一个参数innodb_lock_wait_timeout(默认50秒),超时回滚
- 将innodb_deadlock_detect设置为on,发现死锁即回滚某一个事务。
因为方案1中的参数大小不好设置,一般采取方案2。
方案1如果设置太大,处理死锁速度太慢,如果设置太小,会造成误伤。方案2随着并发数增大,开销会迅速增大,每个锁都会检测。
死锁检测要花费大量的CPU资源,如果解决更新热点造成的性能问题呢?
- 关闭死锁检测,业务保证不出现死锁(不推荐,不一定可行)
- 控制并发度,可以利用中间件排队
- 修改业务逻辑,将热点行拆成多行。比如影院的问题,可以给影院建立虚拟账户。