MySQL 三种锁
全局锁
Flush tables with read lock (FTWRL) 对整个数据库加锁,整个库处于只读状态,之后的其他线程例如DML,DDL,TCL等语句将会被阻塞。
全局锁的使用场景是做全库逻辑备份,但让整库处于只读状态,会导致两个问题。
- 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆
- 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog,会导致主从延迟
MySQL官方使用mysql dump -single-transaction,导数据之前会启动一个事务,来确保拿到一致性视图,不支持事务隔离的引擎如MyISAM,只能通过 FTWRL.
全库只读不使用set global readonly=true的原因:
- 在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。
- 在异常处理机制上有差异。执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。
表级锁
1.表锁
上锁:lock tables ...read/write 解锁:unlock tables。
unlock tables可以主动释放锁,也可以在客户端断开的时候自动释放锁。
lock tables,语法除了会限制线程的读写外,还会限定该线程接下来的操作对象。
2.元数据锁(meta data lock)
不需要显式使用,在访问表时会自动加上,MDL的作用是保证读写的正确性。
当对一张表进行DML操作时会加MDL读锁,对表结构做操作时会加DML写锁。
- 读锁之间不互斥,可以多个线程同时对一张表进行增删改查
- 读写锁,写锁之间相互互斥,用来保证表结构操作的安全性。
事务中的MDL锁,在语句开始执行时申请,但是语句结束后并不会马上释放,而是等整个事务提交后释放。
如何安全的给小标加字段?
alter table 语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者 DBA 再通过重试命令重复这个过程。
3.行锁
MySQL行锁在引擎层,由各个引擎自己实现,MyIsAM不支持行锁。行锁就是在表中行记录的锁。
两阶段锁
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。两阶段锁,锁的添加与释放分到两个阶段进行,之间不允许交叉加锁和释放锁。 也就是在事务开始执行后为涉及到的行按照需要加锁,但执行完不会马上释放,而是在事务结束时再统一释放他们。
如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放
死锁和死锁检测
当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。
出现死锁后的策略
- 直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。
- 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。
死锁检测的条件:如果要加锁访问 的行上 有锁,他才要进行死锁检测。
- 一致性读不会加锁,不会进行死锁检测
- 并不是每次死锁检测都会扫描所有事务,比如现在有B在等A,D在等C,现在来了一个E,发现E需要等D,那么E就判断跟D、C是否会形成死锁,这个检测不用管B和A
如果是我们上面说到的所有事务都要更新同一行的场景呢?
每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度O(n^2)
怎么解决由这种热点行更新导致的性能问题呢
- 如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。风险太高,关掉死锁检测意味着可能会出现大量的超时,这是业务有损的
- 控制并发度:在数据库服务器端做并发控制。如果有中间件,可以在中间件实现;如果可以修改MySQL源码,也可以做在MySQL里面,对于相同行的更新,在进入引擎之前排队。这样在 InnoDB 内部就不会有大量的死锁检测工作了。
- 从逻辑上将一行拆分为多行,