MySQL高级(七)、锁机制

一、概述

定义

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

生活案例

打个比方,我们到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,
那么如何解决是你买到还是另一个人买到的问题?

这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,
然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。

锁的分类

从对数据操作的类型(读/写)分:

  • 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
  • 写锁(排它锁):当前写操作没有完成前,他会阻断其他写锁和读锁。

从对数据操作的粒度分:

  • 表锁
  • 行锁

二、表锁

特点

偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
案例:
我们在创建mylock表,其中有一个id字段和name字段,并在该表上加锁测试。该表结构如下所示。

create table mylock(
 id int not null primary key auto_increment,
 name varchar(20)
)engine myisam;

加读锁

lock table mylock read; #在当前会话session1为mylock表添加读锁
show open tables; #查看表加锁情况,加上锁的表在In_use列上会显示1
unlock tables;#释放表锁

加锁效果:读阻塞写
我们另开一个会话session2,并在这两个会话上进行测试。使用lock table 表名 read/write的方式一次只能锁定一个表,在给另一个表加读锁后当前表的读锁会释放

  1. 当前session可以查询该表记录,其他session也可以查询该表记录;
  2. 当前session不能查询未锁定的表,其他session可以查询未锁定的表;
    在这里插入图片描述
  3. 当前session插入或者更新锁定的表都会提示错误 ,其他session插入或者更新锁定的表会一直等待获得锁。
    在这里插入图片描述
    在这里插入图片描述

加写锁

  1. 当前session可以查询、修改、插入该表记录,其他session对该表的一切操作(包括:查询、修改、插入)都会阻塞;
  2. 当前session不能查询未锁定的表,其他session可以查询、修改、插入未锁定的表。

结论

锁类型 自己可读 自己可写 他人可读 他人可写
读锁 × ×
写锁 × ×

结合上表,所以对MyISAM表进行操作,会有以下情况:
1、对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
2、对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。

简而言之,就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞

三、行锁

特点

偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁
行锁支持事务,复习一下老知识:
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。

  • 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
  • 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
  • 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
  • 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

隔离级别:
在这里插入图片描述
MySQL默认的隔离级别为可重复读(Repeatable read)。

案例

创建一个test_innodb_lock的表,其中带有两个字段int型的a和varchar型的b,建表语句如下所示:

create table test_innodb_lock (a int(11),b varchar(16))engine=innodb;
 
insert into test_innodb_lock values(1,'b2');
insert into test_innodb_lock values(3,'3');
insert into test_innodb_lock values(4,'4000');
insert into test_innodb_lock values(5,'5000');
insert into test_innodb_lock values(6,'6000');
insert into test_innodb_lock values(7,'7000');
insert into test_innodb_lock values(8,'8000');
insert into test_innodb_lock values(9,'9000');
insert into test_innodb_lock values(1,'b1');
 
create index test_innodb_a_ind on test_innodb_lock(a); #注意,行锁一定是要有索引的
create index test_innodb_b_ind on test_innodb_lock(b);

演示过程:
step1:
开启两个会话session1和session2,并且都取消自动提交
在这里插入图片描述
step2:
在session1修改表中a=4的记录,未提交的情况下,在session2中同样修改a=4的记录,查看结果
session1未提交:
在这里插入图片描述
session2阻塞:
在这里插入图片描述
step3:
在session1提交完成后,session2自动提交
session1提交:
在这里插入图片描述
session2自动提交:
在这里插入图片描述
step4:
session1再次提交后查看
在这里插入图片描述
step5:
在session1中修改第5条记录,在session2中修改第9条记录
在这里插入图片描述
在这里插入图片描述
这者互不干扰,可以顺利修改,分别提交后,查看到两条记录都修改了。
在这里插入图片描述
在该案例中有两点需要注意:

  • 因为关闭了自动提交,所以要查看另一个会话已提交的修改,需要先commit;
  • 无索引行锁升级为表锁,如在修改b字段时,故意不写单引号,因为自动类型转换,则索引失效,不能在两个会话分别修改不同的行记录。

间隙锁

【什么是间隙锁】
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,
InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(GAP Lock)。

【危害】
因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。
间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。

【案例】
我们在上述的案例上接着做实验,查看间隙锁的危害。
在这里插入图片描述
先查看该表记录,如上图所示,在a字段中缺少2的记录,我们进行如下的修改
session1修改记录但未提交:
在这里插入图片描述
session2中插入记录,阻塞:
在这里插入图片描述

使用select加锁

加共享锁

共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

语法
SELECT … LOCK IN SHARE MODE;
如:

#为表的第8和9行加上共享锁
select * from test_innodb_lock where a in (8, 9) lock in share mode;

在查询语句后面增加 LOCK IN SHARE MODE ,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的行,而且这些线程读取的是同一个版本的数据。

加排他锁

排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。

语法
SELECT … FOR UPDATE;

在查询语句后面增加 FOR UPDATE ,Mysql会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。

行锁总结

使用show status like ‘innodb_row_lock%’;命令查看分析系统上的行锁的争夺情况
在这里插入图片描述
各参数说明:
Innodb_row_lock_current_waits:当前正在等待锁定的数量;
Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
Innodb_row_lock_time_avg:每次等待所花平均时间;
Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间;
Innodb_row_lock_waits:系统启动后到现在总共等待的次数;

优化建议:

  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
  • 尽可能较少检索条件,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度
  • 锁住某行后,尽量不要去调别的行或表,赶紧处理被锁住的行然后释放掉锁
  • 涉及相同表的事务,对于调用表的顺序尽量保持一致
  • 在业务环境允许的情况下,尽可能低级别事务隔离
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章