工作面试老大难 - 锁
一、概述
为保证数据的一致性和完整性,需要对 事务间并发操作进行控制 ,因此产生了 锁 。锁冲突 也是影响数据库 并发访问性能 的一个重要因素。所以锁对数据库而言显得尤其重要,也更加复杂。
二、并发问题
- MySQL并发事务访问相同记录
(1)读读情况
允许这种情况的发生
(2)写-写情况
该图描述:当一个事务想对这条记录做改动时,首先会看看内存中有没有与这条记录关联的 锁结构 ,当没有的时候就会在内存中生成一个 锁结构 与之关联。等其它事务再次访问该条记录时候,若已经有锁与之关联,那么就需要等待,自身的锁结构为true。
(3)读写情况
即一个事务进行读取操作,另一个进行改动操作。这种情况下可能发生 脏读 、 不可重复读 、 幻读 的问题。注意: MySQL在 REPEATABLE READ 隔离级别上就已经解决了 幻读 问题。 - 并发问题的解决方案
(1)方案一:读操作利用多版本并发控制( MVCC ,下章讲解),写操作进行 加锁 。
(2)方案二:读、写操作都采用 加锁 的方式。
两个方案对比:
(1)采用 MVCC 方式的话, 读-写 操作彼此并不冲突, 性能更高 。
(2)采用 加锁 方式的话, 读-写 操作彼此需要 排队执行 ,影响性能。
三、锁的分类(不同角度)
从数据操作的类型划分:读锁、写锁
- 读锁 :也称为 共享锁 、英文用 S 表示。针对同一份数据,多个事务的读操作可以同时进行而不会
互相影响,相互不阻塞的。 - 写锁 :也称为 排他锁 、英文用 X 表示。当前写操作没有完成前,它会阻断其他写锁和读锁。
注意:对于 InnoDB 引擎来说,读锁和写锁可以加在表上,也可以加在行上
对读取的记录加X锁:SELECT … LOCK IN SHARE MODE; SELECT … FOR SHARE;
对读取的记录加S锁:SELECT … FOR UPDATE;
从锁粒角度划分
表级别
- 表级别的 X锁、S锁:普通的读锁、写锁。注意: InnoDB 存储引擎提供的表级 S锁 或者 X锁 是相当鸡肋,只会在一些特殊情况下,比方说崩溃恢复过程中用到。
- 表级别的 意向锁:给更大一级别的空间(数据页或数据表)示意里面是否已经上过锁。注意:也就是说其实IS锁和IX锁是兼容的(IX、IX也兼容),并且它们也都与行级别的X锁、S锁兼容
- 表级别的 AUTO-INC锁
InnoDB 存储引擎提供了个 innodb_autoinc_lock_mode 的系统变量。
(1)当 innodb_autoinc_lock_mode = 0,就采用 AUTO-INC 锁,语句执行结束后才释放锁;
(2)当 innodb_autoinc_lock_mode = 2,就采用轻量级锁,申请自增主键后就释放锁,并不需要等语句执行后才释放。
(3)当 innodb_autoinc_lock_mode = 1:对于普通 insert 语句,自增锁在申请之后就马上释放;而对于 类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放;
行级别
行级别的 记录锁
普通的读锁(S)锁、写(X)锁。 对一行记录锁定行级别的 间隙锁
引出:为了解决幻读问题而生
概念:锁定两条记录之间间隙(左开右开),使其中不能插入数据,也就防止了幻读问题产生。
注意:如果对一条记录加了 gap锁 (不论是 共享gap锁 还是 独占gap锁 ),并不会限制其他事务对这条记录加 正经记录锁 或者继续加 gap锁。这也说明了间隙锁只为解决防止插入幻影记录而生。
特例:给最后一条记录或者给Supremum加gap锁之后,可以阻止其他事务插入 number 值在 (20, +∞) 这个区间的新记录行级别的 临键锁
概念:一句话,记录锁与间隙锁的合体,左闭右开
例如这个,在( 3,8 ] 这个区间中在锁还没有释放之前(拥有 gap锁 的该事务提交之前)不能插入记录。也就是,它既能保护该条记录,又能阻止别的事务
将新记录插入被保护记录前边的 间隙 。行级别的 插入意向锁(想要在间隙锁中保护的间隙中插入记录,等待时,会生成一个插入意向锁)
概念:设计 InnoDB 的大叔规定事务在等待的时候也需要在内存中生成一个 锁结构 ,表明有事务想在某个 间隙 中插入新记录,但是现在在等待。
注意:插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁
页级别
- 页锁
页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。
每个层级的锁数量是有限制的,因为锁会占用内存空间, 锁空间的大小是有限的 。当某个层级的锁数量
超过了这个层级的阈值时,就会进行 锁升级 。锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如
InnoDB 中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了。
从锁的态度划分
悲观锁
概念:对数据被其他事务的修改持保守态度,会通过数据库自身的锁机制来实现,从而保证数据操作的排它性。每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 阻塞 直到它拿到锁
乐观锁
概念:乐观锁认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。不采用数据库自身的锁机制,而是通过程序来实现。
思路:一条记录,事务A读一次数据,version是1,然后进行修改,判断version是否为1,如果读的时候version是1,改的时候还是1,那么这就说明,在两次操作之间没有其它事务对该条记录操作。(以版本号机制为例子)
实现方式:
(1)乐观锁的版本号机制
(2)乐观锁的时间戳机制(原理相同)
加锁方式
显示锁
通过特定的语句进行加锁,我们一般称之为显示加锁,例如:
(1)显示加共享锁: select … lock in share mode
(2)显示加排它锁: select … for update
隐式锁
概念:一个事务对新插入的记录可以不显式的加锁(生成一个锁结构),但是由于事务id 这个牛逼的东东的存在,相当于加了一个 隐式锁 。(必须加一个锁的原因,在一个事务中新插入的记录,并发的事务会对该记录进行读、或者写操作,这就造成了脏读、脏写)
事务id起的作用:
(1)对于聚簇索引记录来说,有一个 trx_id 隐藏列,该隐藏列记录着最后改动该记录的 事务id 。那么如果在当前事务中新插入一条聚簇索引记录后,该记录的 trx_id 隐藏列代表的的就是当前事务的事务id ,如果其他事务此时想对该记录添加 S锁 或者 X锁 时,首先会看一下该记录的 trx_id 隐藏列代表的事务是否是当前的活跃事务,如果是的话,那么就帮助当前事务创建一个 X锁 (也就是为当前事务创建一个锁结构, is_waiting 属性是 false ),然后自己进入等待状态(也就是为自己也创建一个锁结构, is_waiting 属性是 true )。
(2)对于二级索引记录来说,本身并没有 trx_id 隐藏列,在二级索引页面的 Page Header 部分有一个 PAGE_MAX_TRX_ID 属性,该属性代表对该页面做改动的最大的 事务id ,如果 PAGE_MAX_TRX_ID 属性值小于当前最小的活跃 事务id ,那么说明对该页面做修改的事务都已经提交了,则此时其他事务直接可以对该记录添加 S锁 或者 X锁, 否则就需要在页面中定位到对应的二级索引记录,然后回表找到它对应的聚簇索引记录,然后再根据该条聚簇索引的trx_id找到这条记录目前所在事务,则会为该事务创建一个 X锁结构,并且自己也创建一个进入等待状态。
其他
全局锁
全局锁就是对 整个数据库实例 加锁。当你需要让整个库处于 只读状态 的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。全局锁的典型使用 场景 是:做 全库逻辑备份 。
全局锁的命令: Flush tables with read lock
死锁
概念:在MySQL中,当多个事务同时请求相同的资源时,可能会发生死锁。死锁是指两个或多个事务互相等待对方释放资源,导致所有事务都无法继续执行的情况。
实际处理:
(1)第一种策略,直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout 来设置。
(2)第二种策略,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务(将持有最少行级排他锁的事务进行回滚),让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为on ,表示开启这个逻辑。
第二种策略成本分析:
(1)如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。但是这种操作本身带有一定的风险,因为业务设计的时候一般不会把死锁当做一个严重错误,毕竟出现死锁了,就回滚,然后通过业务重试一般就没问题了,这是 业务无损 的。而关掉死锁检测意味着可能会出现大量的超时,这是业务有损 的。
(2)控制并发度。如果并发能够控制住,比如同一行同时最多只有10个线程在更新,那么死锁检测的成本很低。基本思路就是,对于相同行的更新,在进入引擎之前排队,这样在InnoDB内部就不会有大量的死锁检测工作了。
避免死锁
- 尽量减少事务的持有时间,尽快释放锁。
- 尽量减少事务中需要锁定的资源数量,避免同时请求相同的资源。
- 尽量按照相同的顺序请求资源,避免交叉锁定。
- 使用合适的隔离级别,例如使用READ COMMITTED隔离级别可以减少死锁的发生。
- 对于复杂的事务,可以使用分布式事务管理器来协调多个事务的操作,避免死锁的发生。
四、锁内存结构
五、锁监控
至此,《MySQL是怎样运行的:从根儿上理解MySQL》,复习结束!
工作面试老大难 - 锁相关推荐
- 前端工作面试问题(下)
续 "前端工作面试问题(上)" JS相关问题: 解释下事件代理. 在传统的事件处理中,你按照需要为每一个元素添加或者是删除事件处理器.然而,事件处理器将有可能导致内存泄露或者是性能 ...
- 软件开发面试_如何为成功的软件开发工作面试做准备
软件开发面试 Job interviews are stressful for many people. Besides the pressure of getting hired, you have ...
- [转载]工作面试时最难的25个问题
原文地址:工作面试时最难的25个问题作者:zcan 准备是成功的一半 如果你是一个对目前的职位不满意,正着手去在新的一年里找到一个新的职位,这篇文章就是你的一个帮手.工作面试是你去面对未来的老板的征途 ...
- 分布式面试 - 分布式锁的常见问题
分布式面试 - 分布式锁的常见问题 面试题 一般实现分布式锁都有哪些方式?使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? 面试官心理分析 ...
- 程序开发,面试恐惧症_如何克服恐惧并停止讨厌的工作面试
程序开发,面试恐惧症 by Reuben Reyes 由鲁本·雷耶斯(Reuben Reyes) 如何克服恐惧并停止讨厌的工作面试 (How to conquer your fear and stop ...
- 工作面试时最难的25个问题
准备是成功的一半 如果你是一个对目前的职位不满意,正着手去在新的一年里找到一个新的职位,这篇文章就是你的一个帮手.工作面试是你去面对未来的老板的征途中最重要的一个过程.你必须像进行一个击剑锦标赛或着一 ...
- 前端工作面试问题(上)---转
前端工作面试问题(上) 前段时间专心整理一下关于前端的面试问题.感谢耐心尽责的楷豪和闻东师兄最近给我们的指导和建议.大家可以通过这些问题,大家可以顺便看以下自己的水平. https://github. ...
- 啥?以后找工作面试求职者的将不是人!那是啥?道翰天琼认知智能机器人平台API接口为您揭秘。
啥?以后找工作面试求职者的将不是人!那是啥?道翰天琼认知智能机器人平台API接口为您揭秘. 当你正襟危坐在屏幕前,参加公司视频面试的时候,此时盯着你的不仅是面试官,还有背后一整套 AI 算法. 你的表 ...
- 常见英语面试问答_40个常见的工作面试问答
常见英语面试问答 Every job interview has some common questions that you should be prepared to answer. Having ...
最新文章
- Fescar 发布 0.3.0 版本, 支持 Eureka 注册中心
- Typora 收费,WTF? 还是需要支持下
- Linux下编译cscope,linux环境下cscope使用
- Linux-Learning
- php mysql主从延迟_如何解决主从数据库同步延迟问题?php连接 mysql 数据库如何添加一个公共的配置文件50...
- python取反函数_Python优雅的反函数int(string,base)
- 易成新能加码光伏产业链 作价28.29亿收购赛维两子公司
- UITableView 系列一 :基本使用方法 (显示,删除,添加图片,添加样式等) (实例)...
- 实验8 群体类、流类库与输入/输出(4学时)
- 7.3通过JVM来监控Spring Boot
- AtCoder Beginner Contest 083
- Java 7:最新特性更新、代码示例及性能测试
- Tensorflow游乐场
- 三点估算 Sigma(σ)的值,期望值,标准差
- 地图染色(四色定理)问题
- 提高元认知能力时刻掌握方向舵主动控制生命航向
- Java 实现四位数的吸血鬼算法
- oracle blob 照片,要在oracle里面存入图片 用 blob类型
- 摄像头的像素与分辨率之间的关系
- 如何用织梦仿制php网站首页,DEDE织梦网站首页(排名)仿制实战操作