1.锁概括

锁是计算机协调多个进程或线程并发访问某一个资源的机制

下面主要介绍 MYISAM的表锁和InnoDB的行锁

2.MYISAM表锁

2.1 查询表级锁争用情况

MYISAM表的读操作与写操作之间,以及写操作之间都是串行的(串行:一个个按顺序来,只有前一个执行完,后面才执行)

image.png

2.2MYSQL表级锁的锁模式

有两种模式:表共享读锁和表独占写锁
表兼容性查看p318
对MYISAM表的读操作,不会阻塞其他用户对表的读请求,但会阻塞对同一个表的写请求
对于MYISAM表的写操作,则会阻塞其他用户对同一表的读写操作

2.3如何加表锁

在select之前会给表加读锁,在update、delete、insert会给表叫写锁
给表显示加锁后,必须同时取得所有涉及表的锁,就是当前用户只能访问显示加锁的表,这也就是MYISAM表不会出现死锁的原因
image.png

image.png

2.4 并发操作

MYISAM引擎有一个系统变量 concurrent_insert=
0:不允许并发操作
1:如果表没空洞,MYISAM运行进程读表的同时,当前 用户可以对表进行查询操作,但是不能对表进行跟新操作。其他线程,不能对表进行删除和更新操作,允许另一个进程从表尾插入记录(MySQL默认设置)。
2:有没有空洞都可以插入

利用该特性来解决应用中对同一表查询和插入的锁争用

2.5MYISAM的锁调度

在MYISAM表中,两个用户同时或者不同时分别请求读锁和写锁,写进程都会先获得锁

  1. 通过指定启动参数 low-priority-updates,使MYISAM默认读优先
  2. 执行命令 set LOW_PRIORITY_UPDATES=1 使该连接发出的更新请求优先级降低
  3. 通过指定 IUD语句的LOW_PRIORITY属性,降低该语句的优先级
  4. 给系统参数max_write_lock_count 设置一个合适的值,当写请求达到这个值后,MySQL就暂时将写请求的优先级降低

3.InnoDB锁问题

InnoDB和MYISAM的最大不同两点:一是支持事务,二是采用了行级锁

3.1背景知识

https://blog.csdn.net/dengjili/article/details/82468576
https://blog.csdn.net/Somhu/article/details/78775198
https://www.cnblogs.com/MrSi/p/9439336.html 试验

更新操作:多个事务选择同一行修改,不断的覆盖
脏读:另一个事务B访问了一个修改了但为提交的数据的事务A,事务A最后又没修改,造成事务B读取的数据和表中的数据不一致(侧重select 同一条数据)
不可重复读:一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,该数据发生了改变或某些数据被删除了。 (侧重update delete 数据改变了
幻读:一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足去查询条件的新数据(侧重于insert 数据增加了)

3.2获取InnoDB行锁争用情况

image.png

image.png

image.png

3.3InnoDB的行锁模式及加锁模式

两种类型的锁:
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁
排他锁(X):允许获得排他锁的事务更新操作,阻止其他事务获得相同数据集的共享锁和排他写锁

为了允许行锁和表锁共存,实现多粒度锁机制 还有两种内部使用的意向锁,下面的两种意向锁都是表锁
意向共享锁(IS):事务打算个数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁
意向排他锁(IX):事务打算个数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁

兼容表在P328
意向锁是InnoDB自动加的,不需用户干预,upi语句,InnoDB会自动给涉及的数据集加排他锁(X),对于s语句,InnoDB不会加任何锁;

  1. select * from table_name where ... LOCK IN SHARE MODE
  2. #事务获得共享锁
  3. select * from table_name where ... FOR UPDATE
  4. #事务通过此语句给记录集加排他锁

当前用户加了共享锁后,其他session任然可以查询记录,并也可以对该记录加share mode的共享锁,如果当前事务需要进行更新操作,则很有可能造成死锁

对当前记录加了排他锁后,其他session用户可以查询该记录,但是不能对该记录加排他锁,会等待获得锁,当前session可以对锁定的记录进行更新操作,更新后释放锁
image.png

3.4 InnoDB行锁实现方式

InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁。
Record lock:对索引项加锁
Gap lock:对索引项之间的“间隙”,第一条记录前的“间隙”或最后一条记录后的“间隙”加锁
Next-key lock:前两种的组合,对记录及前面的间隙“加锁”;

如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁 ,实际效果和表锁一样

3.4.1 在不通过索引条件查询时,InnoDB会锁定表中的所有记录

如果表没有索引,然后只给一行加索引,相当于给整个表加了锁,通过索引条件查询时,InnoDB会锁定了符合条件的行
image.png

3.4.2 使用相同索引键,会发生冲突

MySQL的行锁是针对索引加锁,不是针对记录加的锁,所有虽然访问不同行锁的记录,但是使用相同索引键,会发生冲突
image.png

3.4.3 不用索引访问相同的行然后加锁,会发生冲突

不同事务可以使用不同的索引锁定不同的行,不论是使用主键索引…, InnoDB都会使用行锁来对数据加锁

3.4.4 是否对索引加锁,看查询的时候是否用到了索引

如果使用到了索引则给返回的行加锁,反之则会对所有记录加锁
eg:检索值的数据类型与索引字段不同,虽然MySQL能够进行数据类型转换,但却不会用到索引,,从而导致InnoDB对所有记录加锁

3.5 NEXT-KEY锁

给索引条件进行范围查找,值存在加锁,值不存在(间隙)也加锁
一方面防止幻读,另外满足其恢复和复制的需要
image.png

image.png

image.png

3.6恢复和赋值的需要,对InnoDB锁机制的影响

基于语句的日志格式 必须满足在一个事务未提交,其他并发事务不能插入满足其锁定条件的任何记录
对于:
insert … select …
create table … select语句时
当把innodb_locks_unsafe_for_binlog的值为’on’ 当一个事务做插入查询操作时未提交,另一个事务对查询的表其锁定的条件做了更新操作,虽然结果满足逻辑,但是BINLOG的内容是更新在前插入在后。
当把innodb_locks_unsafe_for_binlog的值为’off’ 会给查询操作一个共享锁(默认)

如果非要满足on情况:

  1. 通过’select * from tablea_name… into outfile’ 和 ‘load data infile…’ 语句组合来间接实现
  2. 使用基于行的BINLOG格式和基于行数据的复制

3.7 InnoDB在不同隔离级别下的一致性读及锁的差异

串行化:最严格的级别
(提交了)重复读:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据(两个事务独立,都提交后才能相互访问)(保证了当前事务不提交,开始前已有的数据不会发生改变)(使用了next-key锁)

(提交了) 提交读:保证了一个事务不会读到另一个并行事务已修改但未提交数据(当前事务修改提交后,另一个事务不提交也能看到)(不能保证在当前事务两次读取的数据一致也就出现了不可重复读)(没有使用next-key锁)

(未提交就能访问)未提交读:事务中修改,即使没有提交,其他事务也可以看到(当前事务不提交也能够被访问)

在repeatable read 隔离级别下,两个线程同时对没有符合该条件记录情况下(相当于给不存在的记录都加了排他锁),那么用select..for update 都可以加拍他锁,但试图插入新数据,就会出现等待,当改用read commited就可以解决了。
image.png

consisten read:一致性读取
none lock:无锁
exclusive lock:排他锁
share lock:共享锁
image.png

  1. isolation level repeatable read
  2. set session tarnsaction isolation level 隔离等级
  3. #通过以上两种方法动态改变隔离级别

3.8 什么时候使用表锁

  1. 事务需要更新大部分数据或全部数据,表比较大
  2. 事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。

3.9 关于死锁

3.9.1两个进程访问两个表的顺序不同,造成死锁

两个事务AB分别在两个表开启了两个锁CD, 事务A去触碰D锁,事务B去触碰C锁 就容易造成死锁

3.9.2 处理大量数据,最好先对数据排序,也可降低死锁

3.9.3在事务中应该直接申请足够级别的锁

3.9.4在reprtable-read 两个事务对相同不存在记录用select..for update都会插入成功,然后两个用户同时插入不存在的值就会造成死锁

将隔离级别改成read committed就OK

3.9.5

当在read committed隔离级别时,两个事务AB对相同不存在记录用select… for update都会插入成功,事务A插入这个不存在的值插入成功,事务B插入时就会等待,事务A提交后,事务B会因为主键冲突而插入失败,但是会获得一个排他锁,如果其他事务来申请这个数据集的排他锁时则会造成死锁

通过show InnoDB status命令来确定最后一个死锁产生的原因