修改方法
systemd unit file 里支持的资源隔离配置项,如常见的:
CPUQuota=value
该参数表示服务可以获取的最大 CPU 时间,value 为百分数形式,高于 100% 表示可使用?1 核以上的?CPU.与 cgroup cpu 控制器?cpu.cfs_quota_us?配置项对应.
MemoryLimit=value
该参数表示服务可以使用的最大内存量,value 可以使用 K, M, G, T 等后缀表示值的大小.与 cgroup?memory 控制器?memory.limit_in_bytes?配置项对应.
READ UNCOMMITTED ? ? ? 未提交读,可以读取未提交的数据.
READ COMMITTED ? ? ? ? 已提交读,对于锁定读(select with for update 或者 for share)、update 和 delete 语句,InnoDB 仅锁定索引记录,而不锁定它们之间的间隙,所以呢允许在锁定的记录旁边自由插入新记录. ? ? ? ? ? ? ? ? ? ?
Gap locking 仅用于外键约束检查和重复键检查.
REPEATABLE READ ? ? ? ?可重复读,事务中的一致性读取读取的是事务第一次读取所建立的快照.
数据范围全集组成
SQL 语句根据条件判断不需要扫描的数据范围(不加锁);
SQL 语句根据条件扫描到的可能需要加锁的数据范围;
以单个数据范围为例,数据范围全集包含:(数据范围不一定是连续的值,也可能是间隔的值组成)
加锁情况与死锁原因分析
为方便大家复现,完整表结构和数据如下:
+c1+ int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (+c1+),
) ENGINE=InnoDB
死锁日志如下:
INSERT INTENTION LOCK
但是插入意向锁是客观存在的,我们可以在官方手册中查到,不可忽略:
Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.
当插入一条记录时,会去检查当前插入位置的下一条记录上是否存在锁对象,如果下一条记录上存在锁对象,就需要判断该锁对象是否锁住了 gap.如果 gap 被锁住了,则插入意向锁与之冲突,进入等待状态(插入意向锁之间并不互斥).最后提醒一下大家这把锁的属性:
① 它不会阻塞其他任何锁;
在学习 MySQL 过程中,一般只有在它被阻塞的时候才能观察到,所以这也是它常常被忽略的原因吧...
GAP LOCK
在此例中,另外一个重要的点就是 gap lock,通常情况下我们说到 gap lock 都只会联想到 REPEATABLE-READ 隔离级别利用其解决幻读.但实际上在 READ-COMMITTED 隔离级别,也会存在 gap lock ,只发生在:唯一约束检查到有唯一冲突的时候,会加 S Next-key Lock,即对记录以及与和上一条记录之间的间隙加共享锁.
通过下面这个例子就能验证:
有个困惑很久的疑问:出现唯一冲突需要加 S Next-Key Lock 是事实,但是加锁的意义是什么?还是说是通过 S Next-Key Lock 来实现的唯一约束检查,但是这样意味着在插入没有遇到唯一冲突的时候,这个锁会立刻释放,这不符合二阶段锁原则.这点希望能与大家一起讨论得到好的解释.
如果是在 REPEATABLE-READ,除以上所说的唯一约束冲突外,gap lock 的存在是这样的:
对于 gap lock,相信 DBA 们的心情是一样一样的,所以我的建议是:
① 在绝大部分的业务场景下,都可以把 MySQL 的隔离界别设置为 READ-COMMITTED;
锁冲突矩阵
前面我们说的 GAP LOCK 其实是锁的属性,另外我们知道 InnoDB 常规锁模式有:S 和 X,即共享锁和排他锁.锁模式和锁属性是可以随意组合的,组合之后的冲突矩阵如下,这对我们分析死锁很有帮助:
多线程开启事务处理.每个事务有多个update操作和一个insert操作(都在同一张表).
默认隔离级别:Repeatable Read
逻辑删除原有数据
插入新的数据
根据现有数据情况,update的时候没有数据被更新
报了非常多一样的错
发现居然有死锁.
根据常识考虑,我每个线程(事务)更新的数据都不冲突,为什么会产生死锁?
带着这个问题,打印mysql最近一次的死锁信息
show engine innodb status
显示如下
发现事务1在等待一个锁
关于锁的描述,出现了 lock_mode , gap before rec , insert intention 等字眼,看不懂说明了什么?说明我关于mysql的锁相关的知识储备还不够.那就开始调查mysql的锁相关知识.
通过搜索引擎,
锁的持有兼容程度如下表
那么再回到死锁日志,可以知道 :
事务1正在获取插入意向锁
再看我们上面的锁兼容表格,可以知道, gap lock和insert intention lock是不兼容的
让我回顾一下gap lock的定义:
间隙锁,锁定一个范围,但不包括记录本身.GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况
那为什么是gap lock,gap lock到底是基于什么逻辑锁的记录?发现自己相关的知识储备还不够.那就开始调查.
ps:当前索引不是普通索引,而且是唯一索引等其他情况,请参考下面资料
MySQL 加锁处理分析
回到我自己的案例中,重新屡一下事务1的执行过程:
因为普通索引
KEY hotel_date_idx ( hotel_id , rate_date )
这段sql会获取一个insert intention lock (waiting)
这段sql也会获取一个insert intention lock (waiting)
重新梳理一下:
gap lock 导致了并发处理的死锁
在mysql默认的事务隔离级别(repeatable read)下,无法避免这种情况.只能把并发处理改成同步处理.或者从业务层面做处理.
共享锁、排他锁、意向共享、意向排他
record lock、gap lock、next key lock、insert intention lock
接上篇 事务隔离级别和幻读 ,留了个坑,没想到竟然过了10天,时间不注意真的过的好快.顺便提下,图片链接是属于网站的,开发自己的图床迫在眉睫,万一哪天迁移就要做很多额外工作,一些概念或者思路用图片表达更直观清楚.
回到正题,之前提到一般情况下MySQL的InnoDB引擎在可重复读的情况下是没法保证不出现幻读的,但实际情况是MySQL可以通过加锁来防止幻读的出现,这种锁定通过Next-key机制来实现,是属于记录锁和间隙锁(Gap锁)的结合.
引申,行级别锁的三种算法:
举个存在唯一索引和辅助索引的例子做说明:
所以呢在另一个事务里执行以下语句都会阻塞,具体分析:
这种锁定情形下,可以执行的包括类似语句:
insert的特殊情况
[1]:<