根据加锁的范围,Mysql里面的锁大致可以分为全局锁、表级锁和行锁
1. 全局锁
- 全局锁就是对整个数据库实例加锁
- mysql提供了一个加全局读锁的方法,命令是:
Flush tables with read lock,当你需要让整个库处于只读状态时,可以使用该命令。以下类型语句会被阻塞- 数据库更新语句(数据库的增删改)
- 数据定义语句(建表,修改表结构)
- 更新类事务的提交语句
- 使用场景:
- 如果不是是所有表使用事务引擎的库使用FTWRL:全库逻辑备份也就是把整库每个表都select出来存成文本
- 如果是所有表使用事务引擎的库,全库备份可以使用
mysqldump --single-transaction,备份之前会启动一个事务,来确保拿到一致性视图,而由于MVCC机制,这个过程的数据是可以正常更新的
- 使用全局锁备份数据,使得整库只读:
- 如果是在主库上备份,备份期间都不能执行更新,业务基本停机
- 如果是在从库上备份,备份期间不能执行主库同步过来的binlog,导致主从延迟
- 加全局锁的目的是:在备份期间,read view视图是一致的(有点类似可重复隔离级别)
- 全库只读,还可以通过
set global readonly=true的方式,不推荐的原因- readonly可能会有其他逻辑,比如判断是主库还是从库,修改全局变量风险较高
- 在异常处理机制上:如果是FTWRL之后由于客户端发生异常断开,mysql会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为readonly后,如果客户端发生异常,则数据库会一直保持readonly状态,会导致整个库长时间处于不可写状态,风险较高
2. 表级锁
- Mysql的表级锁分为两种:表锁和元数据锁(meta data lock,MDL)
- 表锁主要分为读写锁:
- 表锁的语法是
lock tables ... read/write,释放锁是unlock tables,也可以在客户端断开时自动释放。 - lock tables语法除了限制别的线程读写外,也限定了本线程接下来的操作对象
- 比如线程t1申请了表t_a的读和t_b的写锁,那么其他线程对于表t_a只能获取读锁,如果要对t_a写或者t_b的读写都会阻塞;并且t1也只能对t_a读操作,以及读写t_b表,对t_a的写操作是不允许的。具体原因可以参考下图
- 比如线程t1申请了表t_a的读和t_b的写锁,那么其他线程对于表t_a只能获取读锁,如果要对t_a写或者t_b的读写都会阻塞;并且t1也只能对t_a读操作,以及读写t_b表,对t_a的写操作是不允许的。具体原因可以参考下图
- 表锁的语法是
- 在还没有更细粒度的锁时,表锁是处理并发的方式,对于Innodb支持行锁的引擎,一般不使用lock tables命令来控制并发,影响面太大。
- MDL锁不需要显式使用,在访问一个表的时候会自动加上。
- 作用是:保持读写的正确性
- 在mysql5.5版本中引入了MDL:对一个表做增删改查时加上MDL读锁,当对表结构变更时加上MDL写锁
- MDL系统默认会加,但是给小表加字段也有可能导致整个库打挂
- 原因就是公平的读写锁的获取原则。
- 比如,线程A获取读锁,一直不释放读锁。线程B获取写锁,锁池等待。线程C获取读锁,因为线程B在获取写锁,所以线程C也会等待。假设在线程A获取了t1的MDL读锁。线程B修改表结构需要获取MDL写锁,线程C查询表中信息需要获取MDL读锁就会被阻塞。
- 如何安全地给小表加字段:
- 查看
infomation_schema库的innodb_trx表中,可以查到当前执行的事务,如果有长事务,考虑暂停或者kill掉长事务。优化SQL避免长事务,查询语句适当使用事务。 - 如果是热点表,结合并发编程中的锁模型:在获取锁的时候增加超时时间
- 修改表结构时添加等待时间
- 查看
ALTER TABLE t NOWAIT add column
ALTER TABLE t WAITE n add column
3. 行锁
- 行锁就是针对数据表中行记录的锁
两阶段锁
- 在Innodb事务中,行锁是在需要的时候才加上的,但并不是不需要就立刻释放,而是要等到事务结束才释放
- 如果一个事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放
死锁和死锁检测
A deadlock is a situation where different transactions are unable to proceed because each holds a lock that the other needs. Because both transactions are waiting for a resource to become available, neither ever release the locks it holds.
- 产生死锁的必要条件,只要一个不成立,死锁就不会发生
- 互斥条件:进程对所分配到的资源进行排他性使用,在一段时间内,某资源只能被一个进程占用;如果此时还有其他进程请求该资源,请求进程只能等待,直至占有该资源的进程使用完释放
- 请求和保持条件:进程至少已经保持了一个资源,但又提出了新的资源请求,而且该资源已经被其他进程占有,此时请求进程被阻塞。但对自己已经获得的资源保持不放
- 不可抢占条件:进程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时由自己释放
- 循环等待条件:在发生死锁时,必然存在一个进程-资源循环链,即:进程集合{P1,P2,...Pn}中的P1等待P2占用的资源,P2等待P3占用的资源,P3等待已经被P1占用的资源。
- 解决死锁的方式:
- 设置获取锁的超市时间:通过
innodb_lock_wait_time来设置(默认50s) - 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数
innodb_deadlock_detect设置为on表示启用这个逻辑
- 设置获取锁的超市时间:通过
- CPU利用率很高,每秒执行不了几个事务,可能在进行死锁检测
-
解决方案:类似java中ConcurrentHashMap中的分段锁,可以考虑把把一行记录分散到不同的记录里,减少热点数据(redis也可以采用此方法设计)
-