Mysql锁(LBCC)

锁的粒度

InnoDB执行行锁和表锁
MyISAM只支持表锁
锁定力度:表锁>行锁
加锁效率:表锁>行锁
冲突概率:表锁>行锁
并发性能:表锁<行锁

定义

LBCC是Lock-Based Concurrent Control的简称,意思是基于锁的并发控制。在InnoDB中按锁的模式来分的话可以分为共享锁(S)、排它锁(X)和意向锁,其中意向锁又分为意向共享锁(IS)和意向排它锁(IX)(此处先不做介绍,后期会专门出篇文章讲一下InnoDB和Myisam引擎的锁);如果按照锁的算法来分的话又分为记录锁(Record Locks)、间隙锁(Gap Locks)和临键锁(Next-key Locks)。其中临键锁就可以用来解决RR下的幻读问题。那么什么是临键锁呢?继续往下看。
Mysql锁(LBCC)_第1张图片
我们将数据库中存储的每一行数据称为记录。则上图中1、5、9、11分别代表id为当前数的记录。对于键值在条件范围内但不存在的记录,叫做间隙(GAP)。则上图中的(-∞,1)、(1,5)…(11,+∞)为数据库中存在的间隙。而(-∞,1]、(1,5]…(11,+∞)我们称之为临键,即左开又闭的集合。

锁的算法

记录锁(Record Locks)

对表中的行记录加锁,叫做记录锁,简称行锁。可以使用sql语句select … for update来开启锁,select语句必须为精准匹配(=),不能为范围匹配,且匹配列字段必须为唯一索引或者主键列。也可以通过对查询条件为主键索引或唯一索引的数据行进行UPDATE操作来添加记录锁。

★ 记录锁存在于包括主键索引在内的唯一索引中,锁定单条索引记录。

间隙锁(GAP Locks)

对上面说到的间隙加锁即为间隙锁。间隙锁是对范围加锁,但不包括已存在的索引项。可以使用sql语句select … for update来开启锁,select语句为范围查询,匹配列字段为索引项,且没有数据返回;或者select语句为等值查询,匹配字段为唯一索引,也没有数据返回。

间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。以下是加锁之后,插入操作的例子:

select * from user where id > 15 for update;
//插入失败,因为id20大于15,不难理解
insert into user values(20,'20');
//插入失败,原因是间隙锁锁的是记录间隙,而不是sql,也就是说`select`语句的锁范围是(11,+∞),而13在这个区间中,所以也失败。
insert into user values(13,'13');

★ GAP Locks只存在于RR隔离级别下,它锁住的是间隙内的数据。加完锁之后,间隙中无法插入其他记录,并且锁的是记录间隙,而非sql语句。间隙锁之间都不存在冲突关系

临键锁(Next-Key Locks)

当我们对上面的记录和间隙共同加锁时,添加的便是临键锁(左开右闭的集合加锁)。为了防止幻读,临键锁阻止特定条件的新记录的插入,因为插入时要获取插入意向锁,与已持有的临键锁冲突。可以使用sql语句select … for update来开启锁,select语句为范围查询,匹配列字段为索引项,且有数据返回;或者select语句为等值查询,匹配列字段为索引项,不管有没有数据返回。

★ 插入意向锁并非意向锁,而是一种特殊的间隙锁。

锁的类型

插入意向锁

允许多个事务同时插入数据到同一个范围 比如数据范围是4-7 两个事务分别插入5,6 不会发生锁等待

自增锁

防止字段重复 数据插入以后就会释放不用等待事务提交之后在释放

共享锁

Mysql锁(LBCC)_第2张图片
select * from user in share mode
select * from user where id =3 lock in share mode
读锁:获取一个数据行的读锁之后可以进行读操作 但是注意不要写 要么会造成死锁,多个事务可以共享一把读锁
作用:阻塞其他事务的修改 用在不允许其他事务修改的情况下

排他锁

Mysql锁(LBCC)_第3张图片
select * from user for update
用来操作数据的 也叫写锁 只要事务获取了这一行数据的排他锁 其他事务都不能修改或者读取
手工加锁: for update

意向锁

原因:
因为加表锁的时候需要去判断是否加了行锁 就需要遍历表里所有行 所以就需要加个意向锁就相当于有个标记
我们给一行数据加上共享之后会默认加一个意向共享锁给一行数据加排他锁之后会默认加意向排他锁
Mysql锁(LBCC)_第4张图片

meta data lock(元数据锁)

解决的问题

为了解决DDL操作和DML操作之间操作一致性

原理

其实MDL加锁过程是系统自动控制,无法直接干预,也不需要直接干预,当我们对一个表做增删改查操作的时候,会自动加MDL读锁;当我们要更新表结构的时候,加MDL写锁。加读锁则所有线程可正常读表的元数据,并且读锁不影响表的增删改查操作,只是不能修改表结构;而加写锁只有拥有锁的线程可以读写元数据,即只拥有锁的线程才能更新表结构,其他线程不能修改结构也不能执行相应的增删改查。

锁的原理

1.没有索引的情况下会锁表 没有索引会进行全表扫描 所以扫表
2.有索引的情况下锁住的是这行数据
3.如果没有聚簇索引会有个默认的rowid锁住
4.唯一索引给数据加锁 主键索引也会被锁住 因为在辅助索引需要先根据 name找到那一行id然后再通过id找到存放数据的地方

加锁的规则

两个原则

1.加锁的基本单位是next-key lock(临间锁)
2.查找过程中访问到对象才会加锁

两个优化

1.索引上的等值查询 给唯一索引加锁的时候 next-key lock 退化成行锁
2.索引上的等值查询 向右遍历时 最后一个值不满足等值条件的时候 next-key-lock退化成间隙锁

一个Bug

唯一索引的范围查询会访问到不满足条件的第一个值为止

死锁

原因

当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致 这几个线程都进入无限等待的状态,称为死锁
Mysql锁(LBCC)_第5张图片
这时候,事务A在等待事务B释放id=2的行锁,而事务B在等待事务A释放id=1的行锁。 事务A和
事务B在互相等待对方的资源释放,就是进入了死锁状态

死锁出现后的策略

1.超时等待
直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout来设置
在InnoDB中,innodb_lock_wait_timeout的默认值是50s
2.主动回滚死锁链条中的某一个事务
发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事 务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑,这个会造成每个事务被锁之后还要判断自己的加入是否会导致死锁然后占用cpu资源 cpu飙升也执行不了几个事务

解决方案

1.如果能确定业务不会出现死锁 可以临时把死锁检测关掉
2.控制并发度
3.修改mysql 源码…对于相同行的更新在进入引擎之前排队
4.你可以考虑通过将一行改成逻辑上的多行来减少锁冲突。还是以影院账户为例,可以考虑放在多
条记录上,比如10个记录,影院的账户总额等于这10个记录的值的总和。这样每次要给影院账
户加金额的时候,随机选其中一条记录来加。这样每次冲突概率变成原来的1/10,可以减少锁等
待个数,也就减少了死锁检测的CPU消耗。

预防死锁的方法

1.对索引加锁顺序的不一致很可能会导致死锁, 所以如果可以, 尽量以相同的顺序来访问索引记录和表. 在程序以批量方式处理数据的时候, 如果事先对数据排序, 保证每个线程按固定的顺序来处理记录, 也可以大大降低出现死锁的可能.
2.间隙锁往往是程序中导致死锁的真凶, 由于默认情况下 MySQL 的隔离级别是 RR,所以如果能确定幻读和不可重复读对应用的影响不大, 可以考虑将隔离级别改成 RC, 可以避免 Gap 锁导致的死锁.
3.为表添加合理的索引, 如果不走索引将会为表的每一行记录加锁, 死锁的概率就会大大增大.
4.避免大事务, 尽量将大事务拆成多个小事务来处理. 因为大事务占用资源多, 耗时长, 与其他事务冲突的概率也会变高.
5.避免在同一时间点运行多个对同一表进行读写的脚本, 特别注意加锁且操作数据量比较大的语句.
6.设置锁等待超时参数:innodb_lock_wait_timeout,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。

你可能感兴趣的:(mysql,mysql,数据库,sql)