当前位置: 首页 > news >正文

mysql锁机制入门笔记(备份)

MySQL锁

事务的 隔离性 由这章讲述的 来实现。

1 锁概述

是计算机协调多个进程或者线程并发访问某一个资源的机制。在程序开发中会存在多线程同步的问题,当多个线程并发访问某一个数据的时候,尤其针对一些敏感的数据(比如订单、金额等),我们就需要保证这个数据在任何时刻最多只有一个线程在访问,保证数据的完整性和一致性。在开发过程中加锁是为了保证数据的一致性,这个思想在数据库领域中同样很重要。

在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。为保证数据的一致性,需要对并发操作进行控制 ,因此产生了锁 。同时锁机制也为实现MySQL的各个隔离级别提供了保证。 锁冲突 也是影响数据库 并发访问性能 的一个重要因素。

所以锁对数据库而 言显得尤其重要,也更加复杂。

2 并发事务带来的问题

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。

脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

丢失修改(Lost to modify):指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。

不可重复读(Unrepeatableread):指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务修改了该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

3 并发事务的解决方案

解决方案:对事务进行隔离

MySQL的四种隔离级别如下:

读未提交(READ UNCOMMITTED):这个隔离级别下,其他事务可以看到本事务没有提交的部分修改。因此会造成脏读的问题(读取到了其他事务未提交的部分,而之后该事务进行了回滚)。这个级别的性能没有足够大的优势,但是又有很多的问题,因此很少使用。

sql演示:

# 创建数据库表
create table goods_innodb(id int NOT NULL AUTO_INCREMENT,name varchar(20) NOT NULL,primary key(id)
)ENGINE=innodb DEFAULT CHARSET=utf8;# 插入数据
insert into goods_innodb(name) values('a');
insert into goods_innodb(name) values('b');# 会话一
set session transaction isolation level read uncommitted ;		# 设置事务的隔离级别为read uncommitted
start transaction ;												# 开启事务
select * from goods_innodb ;									# 查询数据# 会话二
set session transaction isolation level read uncommitted ;		# 设置事务的隔离级别为read uncommitted
start transaction ;												# 开启事务
update goods_innodb set name = 'aa' where id = 2 ;			   # 修改数据# 会话一
select * from goods_innodb ;									# 查询数据

image-20251115091428455

image-20251115091729993

image-20251115092006248

读已提交(READ COMMITTED):其他事务只能读取到本事务已经提交的部分。这个隔离级别有不可重复读的问题,在同一个事务内的两次读取,拿到的结果竟然不一样,因为另外一个事务对数据进行了修改。

sql演示:

# 会话一
set session transaction isolation level read committed;		# 设置事务的隔离级别为read committed
start transaction;												# 开启事务
select * from goods_innodb;									# 查询数据# 会话二
set session transaction isolation level read committed;		# 设置事务的隔离级别为read committed
start transaction;												# 开启事务
update goods_innodb set name = 'aa' where id = 2;			   # 修改数据# 会话一
select * from goods_innodb ;									# 查询数据# 会话二
commit;															# 提交事务# 会话一
select * from goods_innodb ;									# 查询数据

image-20251115093234241

image-20251115093500493

image-20251115093554117

REPEATABLE READ(可重复读):可重复读隔离级别解决了上面不可重复读的问题(看名字也知道),但是不能完全解决幻读。MySql默认的事务隔离级别就是:REPEATABLE READ

SELECT @@global.transaction_isolation ;

image-20251115094355677

sql演示(解决不可重复读):

# 会话一
set session transaction isolation level REPEATABLE READ;
start transaction ;												# 开启事务
select * from goods_innodb;									# 查询数据# 会话二
set session transaction isolation level REPEATABLE READ ;
start transaction ;												# 开启事务
update goods_innodb set name = 'chen' where id = 1;			   # 修改数据# 会话一
select * from goods_innodb ;									# 查询数据# 会话二
commit;															# 提交事务# 会话一
select * from goods_innodb ;									# 查询数据

image-20251115094607743

image-20251115094803034

image-20251115094910559

image-20251115095034828

幻读(Phantom Read)只会在 UPDATE/DELETE 出现,而普通 SELECT 查询不会出现

sql演示(测试出现幻读的情况):

# 表结构进行修改
ALTER TABLE goods_innodb ADD version int(10) NULL;# 会话一
set session transaction isolation level REPEATABLE READ ; #同一个窗口不用反复开
start transaction;												# 开启事务
select * from goods_innodb where version = 1;					# 查询一条不满足条件的数据# 会话二
set session transaction isolation level REPEATABLE READ ;
start transaction;												# 开启事务
insert into goods_innodb(name, version) values('vivo', 1);	    # 插入一条满足条件的数据 
commit;															# 提交事务# 会话一
update goods_innodb set name = 'gj' where version = 1; 		   # 将version为1的数据更改为'金立'
select * from goods_innodb where version = 1;					# 查询一条不满足条件的数据

image-20251115101553741

image-20251115102225041

image-20251115102443164

SERIALIZABLE(可串行化):这是最高的隔离级别,可以解决上面提到的所有问题,因为他强制将所以的操作串行执行,这会导致并发性能极速下降,因此也不是很常用。

4 并发事务访问情况说明

并发事务访问相同记录的情况大致可以划分为3种:读-读情况、写-写情况、读-写或写-读情况

4.1 读-读情况

读-读情况,即并发事务相继读取相同的记录 。读取操作本身不会对记录有任何影响,并不会引起什么问题,所以允许这种情况的发生。

4.2 写-写情况

写-写 情况,即并发事务相继对相同的记录做出改动。 在这种情况下会发生 脏写(脏写读取、脏写覆盖) 的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务相继对一条记录做改动时,需要让它们排队执行 ,这个排队的过程其实是通过来实现的。这个所谓 的锁其实是一个 内存中的结构 ,在事务执行前本来是没有锁的,也就是说一开始是没有 锁结构 和记录进 行关联的,如图所示:

当一个事务想对这条记录做改动时,首先会看看内存中有没有与这条记录关联的锁结构 ,当没有的时候 就会在内存中生成一个 锁结构 与之关联。比如,事务 T1 要对这条记录做改动,就需要生成一个 锁结构 与之关联:

image-20251115103301634

在锁结构中存在很多的信息,为了简化理解,只把两个比较重要的属性拿出来:

1、trx信息:代表这个锁结构是哪一个事务生成的

2、is_waiting: 代表当前事务是否在线等待

当事务T1改动了这条记录后,就生成了一个锁结构与该条记录关联,因为之前没有别的事务为这条记录加锁,所以is_waiting属性就是false,我们把这个场景就称之为获取锁成功,或者加锁成功。然后就可以继续进行操作了。

在事务T1提交之前,另外一个事务T2也想对该记录做更改,那么先看看有没有锁结构与该条记录关联,发现有一个锁结构与之关联,然后也生成了一个锁结构与这条记录关联,不过锁结构的is_waiting属性就是true,表示当前事务需要等待,我们把这个场景就称之为获取锁失败,或者加锁失败。如下图所示:

image-20251115103323529

当事务T1提交之后,就会把该事务生成的锁结构释放掉,然后看看有没有别的事务在等待获取锁,发现了事务T2还在等待获取锁,所以把事务T2对应的锁结构的is_waiting属性设置为false,然后把该事务对应的线程唤醒,让他继续执行,此时事务T2就算获取到了锁,效果如下所示:

image-20251115103333307

小结几种说法:

1、不加锁 意思就是不需要在内存中生成对应的 锁结构 ,可以直接执行操作。

2、获取锁成功,或者加锁成功 意思就是在内存中生成了对应的 锁结构 ,而且锁结构的 is_waiting 属性为 false ,也就是事务 可以继续执行操作。

3、获取锁失败,或者加锁失败,或者没有获取到锁 意思就是在内存中生成了对应的 锁结构 ,不过锁结构的 is_waiting 属性为 true ,也就是事务需要等待,不可以继续执行操作。

4.3 读-写情况

读-写 或 写-读 ,即一个事务进行读取操作,另一个进行改动操作。这种情况下可能发生 脏读 、 不可重 复读 、 幻读 的问题。

要想解决这些问题就需要使用到到事务的隔离级别,而事务的隔离性的实现原理有两种:

1、使用MVCC:读操作利用多版本并发控制( MVCC ),写操作进行加锁 。

普通的SELECT语句在READ COMMITTED和REPEATABLE READ隔离级别下会使用到MVCC读取记录。

1、在 READ COMMITTED 隔离级别下,一个事务在执行过程中每次执行SELECT操作时都会生成一 个ReadView,ReadView的存在本身就保证

了 事务不可以读取到未提交的事务所做的更改 ,也就 是避免了脏读现象;

2、在 REPEATABLE READ 隔离级别下,一个事务在执行过程中只有 第一次执行SELECT操作 才会 生成一个ReadView,之后的SELECT操作都

复用 这个ReadView,这样也就避免了不可重复读 和部分幻读的问题。

2、读、写操作都采用 加锁 的方式。

小结对比发现:

1、采用 MVCC 方式的话, 读-写 操作彼此并不冲突, 性能更高 。

2、采用 加锁 方式的话, 读-写 操作彼此需要 排队执行 ,影响性能。

一般情况下我们当然愿意采用 MVCC 来解决 读-写 操作并发执行的问题,但是业务在某些特殊情况 下,要求必须采用 加锁 的方式执行。

5 锁的分类

从对数据操作的粒度分 :

1) 表锁:操作时,会锁定整个表。

2)页面锁:操作时,会锁定某一页的数据。

3) 行锁:操作时,会锁定当前操作行。

从对数据操作的类型分:

1) 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。

2) 写锁(排它锁):当前操作没有完成之前,它会阻断其他写锁和读锁。

相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。下表中罗列出了各存储引擎对锁的支持情况:

存储引擎表级锁行级锁页面锁
MyISAM支持不支持不支持
InnoDB支持支持不支持
MEMORY支持不支持不支持
BDB支持不支持支持

MySQL这3种锁的特性可大致归纳如下 :

锁类型特点
表级锁偏向MyISAM 存储引擎,开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁偏向InnoDB 存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!仅从锁的角度来说:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web 应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并查询的应用。

6 MyISAM表锁

MyISAM 存储引擎只支持表锁,这也是MySQL开始几个版本中唯一支持的锁类型。

6.1 加锁特点

MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。

显示加表锁语法:

加读锁 : lock table table_name read;
加写锁 : lock table table_name write

6.2 读锁案例

准备环境

create database demo_03 default charset=utf8mb4;use demo_03;CREATE TABLE `tb_book` (`id` INT(11) auto_increment,`name` VARCHAR(50) DEFAULT NULL,`publish_time` DATE DEFAULT NULL,`status` CHAR(1) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'java','2088-08-01','1');
INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'html','2088-08-08','0');CREATE TABLE `tb_user` (`id` INT(11) auto_increment,`name` VARCHAR(50) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;INSERT INTO tb_user (id, name) VALUES(NULL,'zhangsan');
INSERT INTO tb_user (id, name) VALUES(NULL,'lisi');

1)读锁 读操作

  lock table 表名 read;

image-20251116084606068

image-20251116084947787

2 读锁 增删改操作

image-20251116085805923

image-20251116085945577

当在客户端一中释放锁指令 unlock tables 后 , 客户端二中的 inesrt 语句 , 立即执行 ; table加不加s都一样

6.3 写锁案例

客户端 一 :

1)获得tb_book 表的写锁

lock table tb_book write ;

image-20251116091641604

当在客户端一中释放锁指令 unlock tables 后 , 客户端二中的 select 语句 , 立即执行 ;

image-20251116091813901

6.4 结论

锁模式的相互兼容性如表中所示:

image-20251116091852883

由上表可见:

1、 对MyISAM 表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;

2、对MyISAM 表的写操作,则会阻塞其他用户对同一表的读和写操作;

简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁,则既会阻塞读,又会阻塞写

此外,MyISAM 的读写锁调度是写优先,这也是MyISAM不适合做写为主的表的存储引擎的原因。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。

7 InnoDB行锁(重点)

7.1 加锁特点

行锁特点 :偏向InnoDB 存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

InnoDB 与 MyISAM 的最大不同有两点:一是支持事务;二是 采用了行级锁。

InnoDB 实现了以下两种类型的行锁。

1、共享锁(S):又称为读锁,简称S锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

2、排他锁(X)(主要学习):又称为写锁,简称X锁,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);

对于普通SELECT语句,InnoDB不会加任何锁;

可以通过以下语句显示给记录集加共享锁或排他锁 。

共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X)SELECT * FROM table_name WHERE ... FOR UPDATE

7.2 案例准备工作

create table innodb_lock(id int(11),name varchar(16),sex varchar(1)
)engine = innodb default charset=utf8;insert into innodb_lock values(1,'100','1');
insert into innodb_lock values(3,'3','1');
insert into innodb_lock values(4,'400','0');
insert into innodb_lock values(5,'500','1');
insert into innodb_lock values(6,'600','0');
insert into innodb_lock values(7,'700','0');
insert into innodb_lock values(8,'800','1');
insert into innodb_lock values(9,'900','1');
insert into innodb_lock values(1,'200','0');create index idx_innodb_lock_id on innodb_lock(id);
create index idx_innodb_lock_name on innodb_lock(name);

7.3 行锁基本演示

#关闭自动提交事务  , 事务不提交模拟并发环境
#两个seesion都要设置,关闭自动提交事务
set autocommit=0   

image-20251116094207215

image-20251116094958282

如果对面事务一直不结束, 不释放锁会等待超时

image-20251116095316649

7.4 无索引行锁升级为表锁

**如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。**一定需要注意索引失效的问题。

查看当前表的索引 : show index from innodb_lock ;

image-20251116101635642

image-20251116101106847

image-20251116101449224

由于 执行更新时 , name字段本来为varchar类型, 我们是作为数字类型使用,存在类型转换,索引失效,最终行锁变为表锁 ;

7.5 间隙锁危害

间隙锁不是由你想锁什么决定的,也不是由你锁了哪些主键决定的,而是由 InnoDB 为了防止“幻读”对范围扫描自动加出来的。只要执行计划扫描了一个区间,这个区间就会被 gap lock。

范围扫描包括:

  • WHERE 子句是范围(< > <= >= BETWEEN)
  • 非唯一索引查找(等值也可能扫描多个记录)
  • 全表扫描但使用了行锁(FOR UPDATE)
  • RR**(Repeatable Read)** 隔离级别下,为了避免幻读而自动加 gap lock

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

示例 :

image-20251116103122244

image-20251116103105996

7.6 InnoDB 行锁争用情况

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: 系统启动后到现在总共等待的次数当等待的次数很高,而且每次等待的时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手制定优化计划。

7.7 总结

InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面带来了性能损耗可能比表锁会更高一些,但是在整体并发处理能力方面要远远高于MyISAM的表锁的。当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势。

但是,InnoDB的行级锁同样也有其脆弱的一面,当我们使用不当的时候,可能会让InnoDB的整体性能表现不仅不能比MyISAM高,甚至可能会更差。

优化建议:

1、尽可能让所有数据检索都能通过索引来完成,避免无索引行锁升级为表锁。

2、合理设计索引,尽量缩小锁的范围

3、尽可能减少索引条件,及索引范围,避免间隙锁

4、尽量控制事务大小,减少锁定资源量和时间长度

5、尽可使用低级别事务隔离(但是需要业务层面满足需求)

http://www.dtcms.com/a/619031.html

相关文章:

  • Snakemake 从入门到实战:生信自动化工作流搭建指南
  • 深入理解CSS布局:从格子布局到响应式栅格系统
  • 做一个电商网站网站源代码下载软件
  • 网站怎么做网上报名河北网站建设方案
  • 珲春建设局网站备案用网站建设方案书
  • 南京小程序开发网站建设网页设计一页多少钱
  • 做投标的网站南平建设集集团网站
  • 以投资思维做网站h5响应式网站开发
  • 做旅游攻略去什么网站密云网站建设公司
  • Linux 中的 CPU。文章 1. 利用率
  • 自然语言处理实战——基于混合专家模型(MoE)的文本生成
  • ps做网站浏览器预览网站备案号显示红色
  • 暗黑模式【闪白】解决方案
  • Spring Boot + Vue 实现一个在线商城(商品展示、购物车、订单)!从零到一完整项目
  • h5可以制作公司网站吗网站用什么框架做
  • AlmaLinux9.6 部署 MariaDB10.11 和 Zabbix7.0 完整教程
  • 东莞市手机网站建设怎么样自己如何做微信小程序
  • 怎么提升网站收录编程培训班学费一般多少钱
  • Git 在团队中的最佳实践--如何正确使用Git Flow
  • 燕郊做网站的安卓程序开发用什么软件
  • 汽车网站建设需要多少钱做网站后期费用
  • Leetcode 3748. Count Stable Subarrays
  • LeetCode Hot100 缺失的第一个正数
  • skywalking中TID
  • 设计公司展厅装修长沙网站搭建seo
  • 私有化部署的gitlab的push failed问题,使用http远程连接(使用token或用户、密码)
  • 人工智能技术- 语音语言- 01 语音识别与合成
  • 枣庄企业网站推广用什么软件做网站hao
  • 网站类型分析招投标网站开发费用
  • 【C语言预处理器全解析】宏、条件编译、字符串化、拼接