ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

第四章 MYSQL优化(4)——锁的机制

2021-02-01 14:59:41  阅读:107  来源: 互联网

标签:事务 加锁 lock 死锁 innodb MYSQL test 优化 第四章


MYSQL优化(4)

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
第一章 MYSQL优化(1)——架构介绍
第二章 MYSQL优化(2)——SQL的优化1
第三章 MYSQL优化(3)——SQL的优化2
第四章 MYSQL优化(4)——锁的机制
第五章 MYSQL优化(5)——主从配置


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


一、概述

1.1什么是锁?

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

1.2锁的分类

  1. 按照锁的粒度划分:行锁、表锁、页锁
  2. 按照锁的使用方式划分:共享锁、排它锁(悲观锁的一种实现)
  3. 还有两种思想上的锁:悲观锁、乐观锁
  4. InnoDB中有几种行级锁类型:Record Lock、Gap Lock、Next-key Lock
  5. Record Lock:在索引记录上加锁
  6. Gap Lock:间隙锁
  7. Next-key Lock:Record Lock+Gap Lock

二、行锁

特点

  1. ​ 行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁
  2. 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。有可能会出现死锁的情况
  3. 行级锁按照使用方式分为共享锁排他锁

行锁支持事务,对事务做一下分析

事务(Transation)及其ACID属性—事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。

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

并发事务处理带来的问题

1.更新丢失(Lost Update):
1 当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题一一最后的更新覆盖了由其他事务所做的更新。
2 例如,两个程序员修改同一java文件。每程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。
3 如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。
2.脏读(Dirty Reads):
1 一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”。
2 一句话:事务A读取到了事务B已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。
3.不可重复读(Non-Repeatable Reads):
1 一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
2 一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性
4.幻读(Phantom Reads)
1 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读一句话:事务A读取到了事务B体提交的新增数据,不符合隔离性。
2 多说一句:幻读和脏读有点类似,脏读是事务B里面修改了数据,幻读是事务B里面新增了数据。

事物的隔离级别

  1. 脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
  2. 数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。
  3. 同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。
  4. 查看当前数据库的事务隔离级别:show variables like ‘tx_isolation’; mysql 默认是可重复读

在这里插入图片描述

案例分析

1.建表SQL

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_lock_b_ind ON test_innodb_lock(b);

2.操作同一条数据-----锁住该条数据,后面操作处于等待,前面操作解锁后方可执行

开启2个session会话
------------------
session1执行:
set autocommit=0;
update test_innodb_lock set b='4001' where a=4;
-----------------
session2执行:
set autocommit=0;
update test_innodb_lock set b='4001' where a=4;
-----------------
结论:session2操作在持续等待session1的commit结束解锁。

3.操作不是同一条数据-----互不干涉

开启2个session会话
------------------
session1执行:
set autocommit=0;
update test_innodb_lock set b='4001' where a=4;
-----------------
session2执行:
set autocommit=0;
update test_innodb_lock set b='4002' where a=5;
-----------------
结论:session1,session2均快速完成各自操作

4无索引导致行锁升级为表锁------原因为InnoDB的行锁作用在索引上不是在数据上

开启2个session会话
------------------
session1执行:
set autocommit=0;
update test_innodb_lock set a=44 where b=4000;
-----------------
session2执行:
set autocommit=0;
update test_innodb_lock set b='9001' where a=9;
-----------------
结论:session2操作在持续等待session1的commit结束解锁。
注:原因-----InnoDB的行锁作用在索引上不是在数据上

手动行锁—用于对特定数据的修改不受其他操作影响

语法:select xxx ... for update 锁定某一行后,其它的操作会被阻塞,直到锁定行的会话提交
-----------------------
开启2个session会话
------------------
session1执行:
set autocommit=0;
select * from test_innodb_lock  where a=8 for update;
------------------
session2执行:
set autocommit=0;
update test_innodb_lock set b='XXX' where a=8;
# 在这儿阻塞着呢~~~

行锁分析

1.SQL语句:
'show status like ‘innodb_row_lock%’;
2.对各个状态量的说明如下:
1.Innodb_row_lock_current_waits:当前正在等待锁定的数量;
2.Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
3.Innodb_row_lock_time_avg:每次等待所花平均时间;
4.Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
5.Innodb_row_lock_waits:系统启动后到现在总共等待的次数;
3.主要关注:
Innodb_row_lock_time_avg(等待平均时长)
Innodb_row_lock_waits(等待总次数)
Innodb_row_lock_time(等待总时长)

优化建议

  1. 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
  2. 合理设计索引,尽量缩小锁的范围
  3. 尽可能较少检索条件,避免间隙锁
  4. 尽量控制事务大小,减少锁定资源量和时间长度
  5. 尽可能低级别事务隔离

三、表锁

特点

  1. 偏向MyISAM存储引擎,
  2. 开销小,加锁快,无死锁,锁定粒度大
  3. 发生锁冲突的概率最高,并发最低

案例分析

1.建表SQL—引擎选择 myisam

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

insert into mylock(name) values('a');
insert into mylock(name) values('b');
insert into mylock(name) values('c');
insert into mylock(name) values('d');
insert into mylock(name) values('e');
select * from mylock;

2.添加锁的SQL

1查看当前数据库中表的上锁情况
show open tables;,0 表示未上锁
---------------------
2添加锁
lock table 表名1 read(write), 表名2 read(write), ...;
---------------------
3.释放表锁
unlock tables;

3添加read锁

1给mylock表加读锁
lock table mylock read;
-------------
开启2个session会话
------------------
2session1执行:
select * from mylock;
自己可以读取自己锁的读锁表
-----------------
3session1执行:
select * from book;
ERROR 1100 (HY000): Table 'book' was not locked with LOCK TABLES
自己不能读其他的表
-----------------
4session1执行:
update mylock set name='a2' where id=1;
ERROR 1099 (HY000): Table 'mylock' was locked with a READ lock and can't be updated
自己不能修改自己加读锁的表
-----------------
5.session2执行:
update mylock set name='a2' where id=1;
# 在这里阻塞着呢~~~ 需要等待解锁后方可修改
-----------------
结论:
1.当前 session 和其他 session 均可以读取加了读锁的表
2.当前 session 不能读取其他表,并且不能修改加了读锁的表
3.其他 session 想要修改加了读锁的表,必须等待其读锁释放

4添加write锁

1给 mylock 表加个写锁
lock table mylock write;
-------------
开启2个session会话
------------------
2session1执行:
select * from mylock;
自己可以读取自己锁的写锁表
-----------------
3session1执行:
select * from book;	
ERROR 1100 (HY000): Table 'book' was not locked with LOCK TABLES
自己不能读其他的表
-----------------
4.session2执行:
 select * from mylock;
# 在这里阻塞着呢~~~ 需要等待解锁后方可修改
-----------------
结论:
1.当前 session 可以读取和修改加了写锁的表
2.当前 session 不能读取其他表
3.其他 session 想要读取加了写锁的表,必须等待其读锁释放

结论

1.MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
/
2MySQL的表级锁有两种模式
1表共享读锁(Table Read Lock)
2表独占写锁(Table Write Lock)
3.MySQL的读写锁总结------就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞。
1对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
2对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作

四、页锁

  1. 开销和加锁时间界于表锁和行锁之间:会出现死锁;
  2. 锁定粒度界于表锁和行锁之间,并发度一般。
  3. 了解即可

五、间隙锁(Gap Lock)

定义

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

问题—对没有的数据插入时进行上锁

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

案例

开启2个session会话
------------------
1session1执行:
set autocommit=0;
update test_innodb_lock set b='Heygo' where a>1 and a<6;
-----------------
3session2执行:
set autocommit=0;
update test_innodb_lock set b='9001' where a=9;
# 在这儿阻塞着呢~~~
-----------------

六、乐观锁和悲观锁

定义

  1. 无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像memcache、hibernate、tair等都有类似的概念。
  2. 针对于不同的业务场景,应该选用不同的并发控制方式。与上述行锁,表锁,页锁,共享锁,排他锁不要混淆。

6.1、乐观锁

定义:对用户比较友好,不会产生任何锁和死锁,只会在业务逻辑上进行等待

  1. 在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。
  2. 多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据
  3. 对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。

优缺点

  1. 乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。
  2. 但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题

6.2、悲观锁

定义:会调用数据库的锁,出现报错等信息,直接进行上锁

优缺点:

  1. 悲观锁实际上是采取了“先取锁在访问”的策略,为数据的处理安全提供了保证
  2. 效率低,降低并发
    3.会出现死锁情况

七、死锁原理及分析

定义:不是一把锁,而是一种现象

7.1.MVCC------多版本并发控制协议

  1. MySQL InnoDB存储引擎,实现的是基于多版本并发控制协议—MVCC(Multi-Version Concurrency Control) MVCC最大的好处,相信也是耳熟能详:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能,这也是为什么现阶段,几乎所有的RDBMS,都支持了MVCC。
  2. InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),没开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID.

7.2.二阶段锁(Two-Phase Locking)

说明:
1 传统RDBMS(关系数据库管理系统)加锁的一个原则,就是2PL (二阶段锁):Two-Phase Locking。相对而言,2PL比较容易理解,说的是锁操作分为两个阶段:加锁阶段与解锁阶段,并且保证加锁阶段与解锁阶段不相交。下面,仍旧以MySQL为例,来简单看看2PL在MySQL中的实现。
2下面的例子可以看出2PL就是将加锁、解锁分为两个阶段,并且互相不干扰。加锁阶段只加锁,解锁阶段只解锁。

transactionmysql
begin加锁阶段
insert into加insert对应的锁
update table加update对应的锁
delete from加delete对应的锁
commit解锁阶段
/将insert、update、delete的锁全部解开

7.3.为什么出现死锁-----InnoDB会出现思索

  1. MyISAM中是不会产生死锁的,因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待。
  2. 而在InnoDB中,锁是逐步获得的,就造成了死锁的可能。(不过现在一般都是InnoDB引擎,关于MyISAM不做考虑)

原因:
1在InnoDB中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
2当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。

通过两个SQL死锁的例子来说明

.情况一、两个session的两条语句------首先session1获得 id=1的锁 session2获得id=5的锁,然后session想要获取id=5的锁 等待,session2想要获取id=1的锁 ,也等待!

在这里插入图片描述

.情况二、两个session的一条语句------​ 假设默认情况是RR隔离级别,针对session 1 从name索引出发,检索到的是(hdc,1)(hdc,6)不仅会加name索引上的记录X锁,而且会加聚簇索引上的记录X锁,加锁顺序为先[1,hdc,100],后[6,hdc,10] 这个顺序是因为B+树结构的有序性。而Session 2,从pubtime索引出发,[10,6],[100,1]均满足过滤条件,同样也会加聚簇索引上的记录X锁,加锁顺序为[6,hdc,10],后[1,hdc,100]。发现没有,跟Session 1的加锁顺序正好相反,如果两个Session恰好都持有了第一把锁,请求加第二把锁,死锁就发生了。
在这里插入图片描述

7.4.避免死锁

  1. 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
  2. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
  3. 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;

标签:事务,加锁,lock,死锁,innodb,MYSQL,test,优化,第四章
来源: https://blog.csdn.net/weixin_48351067/article/details/113175824

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有