Mysql的锁类型、锁模式、加锁方式

    xiaoxiao2022-07-13  154

    Mysql的锁:

    锁类型(lock_type):

    表锁: 通过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 2048

    LOCK_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冲突,也正是这种冲突,阻止了记录的插入,从而避免了幻读。  

    锁模式(lock_mode):

    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 

    最新回复(0)