表锁: 通过Mysql服务实现,加锁:lock table xxx read/write,解锁:unlock tables; 当会话将表加上锁后,在锁释放之前,会话只能访问这些加锁的表
表锁里又可以分为读锁和写锁。
表锁的加锁规则: 读锁: 1.持有读锁的会话可以读表,但不能写表; 2.允许多个会话同时持有读锁,其他会话就算没有给表加读锁,也是可以读表的,但是不能写表,申请写锁时会阻塞 写锁: 1.持有写锁的会话既可以读表,也可以写表; 2.只有持有写锁的会话才可以访问该表,其他会话访问该表会被阻塞,申请读锁或写锁时都会阻塞 表锁的释放规则: 1.unlock tables释放 2.会话在持有表锁的情况下执行lock table语句,会释放掉之前持有的锁 3.会话在持有表锁的情况下执行start transaction或者begin开启一个新事务,会释放掉之前持有的锁 4.会话连接断开,会释放掉之前持有的锁行锁: 由存储引擎(其实也就InnoDB有)实现,作用是为了提高并发能力
行锁的类型: Mysql源码:
#define LOCK_TABLE 16 /* table lock */ #define LOCK_REC 32 /* record lock */ /* Precise modes */ #define LOCK_ORDINARY 0 #define LOCK_GAP 512 #define LOCK_REC_NOT_GAP 1024 #define LOCK_INSERT_INTENTION 2048LOCK_ORDINARY(Next-Key Lock): 记录锁+GAP锁,锁一条记录及其之前的间隙,左开右闭区间,闭的那个就是记录(InnoDB的默认加锁方式是Next-Key Lock,当索引有唯一性质时,Next-Key Lock自动降级为Record Lock)
LOCK_GAP: 间隙锁,锁两记录直接的间隙(GAP),防止insert插入,目的是为了避免幻读
LOCK_REC_NOT_GAP: 记录锁,只锁记录不锁GAP
LOCK_INSERT_INTENTION: 插入意向锁,插入记录时使用,是LOCK_GAP的一种特例,和表级意向锁是两个概念,插入意向锁之间不冲突,但是插入意向锁和Next-Key Lock、GAP LOCK冲突,也正是这种冲突,阻止了记录的插入,从而避免了幻读。
Mysql源码:
/* Basic lock modes */ enum lock_mode { LOCK_IS = 0, /* intention shared */ LOCK_IX, /* intention exclusive */ LOCK_S, /* shared */ LOCK_X, /* exclusive */ LOCK_AUTO_INC, /* locks the auto-inc counter of a table in an exclusive mode*/ ... };首先我们介绍意向锁这个概念:意向锁: 为了方便检测表锁和行锁的冲突而引入的锁,可以算是表级锁,当事务需要读或者某条记录时,需要先在表上加意向锁,然后才能在记录上加读锁或写锁;LOCK_IS:读意向锁;LOCK_IX:写意向锁;
LOCK_S:读锁,又称共享锁;LOCK_X:写锁,又称排他锁;
LOCK_AUTO_INC: 自增锁,当表中有自增列的时候会用到,保证自增列值的唯一性,一种特殊类型的表锁,有如下特点: 1.互不兼容,同一张表任意时刻都最多只能有一个自增锁 2.不遵循2PC,锁在insert语句执行结束时释放(可以提高并发插入性能) 3.自增值不会回滚,所以自增列的值可能会不连续 显然,AUTO_INC表锁会导致并发插入的效率降低,为了提高插入的并发性,MySQL从5.1.22版本开始,引入了一种可选的轻量级锁(mutex)机制来代替AUTO_INC锁,可以通过参数innodb_autoinc_lock_mode控制分配自增值时的并发策略。innodb_autoinc_lock_mode = 0(traditional lock mode) 使用传统的 AUTO_INC 表锁,并发性比较差。 innodb_autoinc_lock_mode = 1(consecutive lock mode) MySQL 默认采用这种方式,是一种比较折中的方法。 MySQL 将插入语句分成三类:Simple inserts、Bulk inserts、Mixed-mode inserts。 通过分析 INSERT 语句可以明确知道插入数量的叫做 Simple inserts,譬如最经常使用的 INSERT INTO table VALUE(1,2) 或 INSERT INTO table VALUES(1,2), (3,4); 通过分析 INSERT 语句无法确定插入数量的叫做 Bulk inserts,譬如 INSERT INTO table SELECT 或 LOAD DATA 等; 还有一种是不确定是否需要分配自增值的,譬如 INSERT INTO table VALUES(1,'a'), (NULL,'b'), (5, 'C'), (NULL, 'd') 或 INSERT ... ON DUPLICATE KEY UPDATE,这种叫做 Mixed-mode inserts。 Bulk inserts 不能确定插入数使用表锁; Simple inserts 和 Mixed-mode inserts 使用轻量级锁 mutex,只锁住预分配自增值的过程,不锁整张表; Mixed-mode inserts 会直接分析语句,获得最坏情况下需要插入的数量,一次性分配足够的自增值,缺点是会分配过多,导致浪费和空洞。 这种模式的好处是既平衡了并发性,又能保证同一条 INSERT 语句分配的自增值是连续的。 innodb_autoinc_lock_mode = 2(interleaved lock mode) 全部都用轻量级锁 mutex,并发性能最高,按顺序依次分配自增值,不会预分配。 缺点是不能保证同一条 INSERT 语句内的自增值是连续的,这样在复制(replication)时,如果binlog_format为statement-based(基于语句的复制)就会存在问题,因为是来一个分配一个,同一条 INSERT 语句内获得的自增值可能不连续,主从数据集会出现数据不一致。所以在做数据库同步时要特别注意这个配置。这几种锁的兼容性(S\X\IS\IX\AI): 1.意向锁之间互不冲突; 2.S锁只和S/IS锁兼容,和其他锁都冲突; 3.X锁和其他所有锁都冲突; 4.AI锁只和意向锁兼容;
先了解一些概念:MVCC(Multi-Version Concurrency Control): 基于多版本的并发控制协议,是可以提高系统并发能力
MVCC中的读:快照读(Snapshot Read):普通的select读,读数据记录的快照版本,不加锁 select * from t where xxx;当前读(Current Read):特殊的select读,读数据记录的最新版本,且在读取完成后需要保证数据不被其他事务修改,因此要加锁 select * from t where xxx for update;---------X锁 select * from t where xxx lock in share mode;---------S锁 update / insert / delete---------X锁
InnoDB与MySQL Server的交互,是一条一条进行的,因此加锁也是一条条加上去的。 update / delete流程: Mysql发起当前读--->InnoDB进行当前读,返回数据并加锁--->Mysql Server发起修改--->InnoDB修改,返回修改状态--->循环 insert流程: 涉及到唯一性索引的校验,所以要发起当前读
两阶段锁协议(Two-Phase Locking、2PC): 事务中涉及到锁操作分为两阶段:加锁阶段和解锁阶段,且两阶段无交集
RR / RC 隔离级别下,针对当前读时锁的状态: Read Uncommited:当前读时,对读取到的记录加锁(记录锁),存在幻读现象。 Read Committed:当前读时,对读取到的记录加锁(记录锁),同时要对读取的范围加锁,保证新的满足查询条件的记录不能够插入(间隙锁),不存在幻读现象。
1.主键访问: RC模式: 只需要给主键上查询对应的记录加锁即可,如无满足条件的则不加锁 RR模式: 1.等值查询:加Next-key LOCK,如果键值满足条件,Next-key LOCK退化为行锁,如果不满足条件,Next-key LOCK退化为GAP锁 2.范围查询:加Next-key LOCK,一直遍历到第一个不满足查询条件的键值为止,且锁不退化(也就是说它甚至会把第一个不满足查询条件的那条记录也锁上。。)2.唯一性二级索引访问: 和主键访问的区别是一个是锁主键索引,一个是锁二级索引+对应的主键记录 3.非唯一性二级索引访问: RC模式: 只需要给二级索引上对应的记录和对应主键索引上的记录加锁 RR模式: 1.等值查询:加Next-key LOCK,一直遍历到第一个不满足查询条件的键值为止,然后把这个Next-key LOCK退化为GAP锁 2.范围查询:加Next-key LOCK,一直遍历到第一个不满足查询条件的键值为止,且锁不退化(也就是说它甚至会把第一个不满足查询条件的那条记录也锁上。。)4.无索引访问(也可以叫做主键索引全扫描、全表扫描): RC模式: 主键上所有记录行加锁,返回给Server层,Server层进行过滤,对不满足条件的记录,调用unlock_row方法放锁 RR模式: 主键上所有记录行和GAP加锁,返回给Server层,Server层进行过滤,且事务结束之前不会释放(保证binlog记录顺序的正确性,且要遵守两段锁协议) 设置innodb_locks_unsafe_for_binlog参数为ON可以使用semi-consistent read(和rc级别那样,对不符合条件的记录及时解锁),但是会带来其他问题(禁用了GAP锁),不建议使用。 针对一个复杂的SQL,首先需要提取其where条件。 Index Key确定的范围,需要加上GAP锁; Index Filter给定的条件何时过滤,视MySQL的版本而定,在MySQL 5.6版本之前,不支持Index Condition Pushdown(ICP),因此Index Filter在MySQL Server层过滤,在5.6后支持了Index Condition Pushdown,则在index上过滤。若不支持ICP,不满足 Index Filter的记录,也需要加上记录X锁,若支持ICP,则不满足Index Filter的记录,无需加记录X锁; Table Filter过滤条件,无论是否满足,都需要加X锁。
不是具体的锁,而是一种锁的思想: 悲观锁需要使用数据库的锁机制来实现,乐观锁是通过程序的手段来实现
另外看了网上这么多例子,发现用rc的比rr多。。然后binlog_format用row
参考(写的都特别详细): https://www.aneasystone.com/archives/2017/11/solving-dead-locks-two.html http://hedengcheng.com/?p=771