《Mysql 锁 - 概述》
一:锁类型(加锁范围区分类型)
- MySQL里面的锁可以分为:全局锁、表级锁、行级锁。
二:全局锁
- 作用
- 对整个数据库实例加锁。
- 加锁方式
- MySQL提供加全局读锁的方法:Flush tables with read lock(FTWRL)
- 这个命令可以使整个库处于只读状态。
- 使用该命令之后,数据更新语句、数据定义语句和更新类事务的提交语句等操作都会被阻塞。
- 使用场景
- 全库逻辑备份。(加锁的目的是为了在备份时候 保证数据视图的一致性)
- 风险(指的使用全局锁风险)
- 如果在主库备份,在备份期间不能更新,业务暂停。
- 如果在从库备份,备份期间不能执行主库同步的binlog,导致主从延迟。
- 其他方法(可以代替全局锁的方法)
- 官方自带的逻辑备份工具mysqldump
- 当mysqldump使用参数--single-transaction的时候,会启动一个事务,确保拿到一致性视图。
- 而由于MVCC的支持,这个过程中数据是可以正常更新的。
- 一致性读是好,但是前提是引擎要支持这个隔离级别。
- 如果要全库只读,为什么不使用set global readonly=true的方式?
- 在有些系统中,readonly的值会被用来做其他逻辑,比如判断主备库。所以修改global变量的方式影响太大。
- 在异常处理机制上有差异。如果执行FTWRL命令之后由于客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态
- 而将整个库设置为readonly之后,如果客户端发生异常,则数据库就会一直保持readonly状态,这样会导致整个库长时间处于不可写状态,风险较高。
三:表级锁
- MySQL里面表级锁有两种
- 表锁(lock table)
- 元数据锁(meta data lock,MDL)
- 表锁
- 作用
- 防止 DDL 之间的冲突
- 加锁方式
- lock tables ... read/write
- 可以用unlock tables主动释放锁,也可以在客户端断开的时候自动释放。
- 注意
- lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
- 例如:线程 A 执行了 lock tables t1 read, t2 write;
- 则其他线程写 t1、读写 t2 的语句都会被阻塞。
- 同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。
- 连写 t1 都不允许,自然也不能访问其他表。
- 元数据锁(MDL)
- 作用
- MDL作用是防止DDL和DML并发的冲突。
- 加锁方式
- 不需要显式使用,在访问一个表的时候会被自动加上。
- 在对一个表做增删改查操作的时候,加MDL读锁。(可并发读)
- 当要对表做结构变更操作的时候,加MDL写锁。(堵塞读写)
- 可能产生的问题
- 以下面的事务举例子
-
- 分析一下上面事务可能产生的问题
- session A 先启动,这时候会对表 t 加一个 MDL 读锁。
- session B 需要的也是 MDL 读锁,因此可以正常执行。
- session C 会被 blocked(阻塞),是因为 session A 的 MDL 读锁还没有释放,而 session C 需要 MDL 写锁,因此只能被阻塞。
- 问题
- 如果只有 session C 自己被阻塞还没什么关系,但是之后所有要在表 t 上新申请 MDL 读锁的请求也会被 session C 阻塞。
- 因为 DML 的特性,所有对表的增删改查操作都需要先申请 MDL 读锁,就都被锁住,等于这个表现在完全不可读写了。
- 如果某个表上的查询语句频繁,而且客户端有重试机制,也就是说超时后会再起一个新 session 再请求的话,这个库的线程很快就会爆满。
- 原因
- 事务中的 MDL 锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放。
- 解决热点更新表的字段添加问题?(知道 MDL 可能产生的问题,我们要尝试解决在热点表添加字段,同时避免 MDL 锁的问题)
- 应当尽量避免在业务高峰时,执行 DDL 语句
- 解决长事务 (事务不提交,就会一直占着 MDL 锁。)
- 在 MySQL 的 information_schema 库的 innodb_trx 表中,你可以查到当前执行中的事务。
- 如果你要做 DDL 变更的表刚好有长事务在执行,要考虑先暂停 DDL,或者 kill 掉这个长事务。
- 但是如果是热点表,请求频繁,而不得不加字段
- 这时候 kill 可能未必管用,因为新的请求马上就来了。
- 比较理想的机制是,在 alter table 语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。
- 之后开发人员或者 DBA 再通过重试命令重复这个过程。
四:行级锁
- 行级锁
- 是在各个 存储引擎里自己实现的。
- 两阶段锁的概念是什么? 对事务使用有什么帮助?
- 概念
- 在 InnoDB 事务中,行锁是在需要的时候才加上的(一段)。
- 但并不是不需要了就立刻释放, 而是要等到事务结束时才释放(二段)。
- 帮助
- 数据视图的一致性,保证隔离性。
- 死锁的概念是什么? 举例说明出现死锁的情况.
- 概念
- 事务 A 和事务 B 在互相等待对方的资源释放,就是进入了死锁状态。
- 死锁(A/B 事务相互等待)
-
-
-
A 事务 B 事务 update A = 1(A 加锁) update B = 1(B 加锁) update B = 2(等待B事务释放锁) update A = 2(等待A事务释放锁) 死锁
-
-
- 死锁的处理策略?
- 死锁等待
- 当事务遇到死锁时候,会一直等待,直到超时。
- 参数 innodb_lock_wait_timeout 设置死锁等待时间(InnoDB默认是50s)。
- 死锁检测
- 当发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。
- 将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑(默认是开启状态)。
- 等待超时处理死锁的机制什么?有什么局限?
- 机制
- 出现死锁后,事务双方相互等待,直到有一个事务超时,另一个事务获得锁,继续执行。
- 局限
- 业务中出现大量的超时事务,而等待锁的过程中也会导致业务等待。
- 死锁检测处理死锁的机制是什么? 有什么局限?
- 机制
- 每当一个事务被锁的时候。就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。
- 局限
- 每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。
- 假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。
- 虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。
- 有哪些思路可以解决热点更新导致的并发问题?(知道了死锁的机制处理之后)
- 如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关闭掉。
- 可能产生大量的超时事务,一般不建议采用
- 控制并发
- 根据上面的分析,你会发现如果并发能够控制住,比如同一行同时最多只有 10 个线程在更新,那么死锁检测的成本很低,就不会出现这个问题。
- 通过中间件或者其他方式,控制并发度,对应相同行的更新,在进入引擎之前排队。这样在InnoDB内部就不会有大量的死锁检测工作了。
- 业务拆分
- 比如把账单的一条数据,拆分成10条,最后求和。更新时候,选择一条进行操作。
- 这样可以减少锁冲突,并发度也会提高,但是业务复杂度可能会大大提高。