百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

MySQL事务和锁的使用(mysql事务和锁之间的关系)

mhr18 2024-10-09 12:23 29 浏览 0 评论

事务

事务使用户定义的一个操作序列,这些操作要么全部失败,是一个不可分割的工作单位(构成单一逻辑工作单元的操作集合)

如果某一事务成功,则该事务中进行的所有数据更改均会提交,成为数据库中永久组成部分。

如果事务遇到错误且必须取消或者回滚,则所有更改均被清除。

事务的执行,要么成功,要么回滚。

逻辑架构和存储引擎

MySQL服务器逻辑架构从上往下可以分为三层:

第一层:处理客户端连接、授权认证等

第二层:服务器层,负责查询语句的解析、优化、缓存以及内置函数的实现、存储过程等。

第三层:存储引擎,负责MySQL中数据的存储和提取

MySQL中服务器不管理事务,事务是由存储引擎实现的。MySQL支持事务的存储引擎有InnoDB、NDB Cluster等,其中InnoDB使用最为广泛;其他存储引擎不支持事务。

事务操作

基本命令:

  • START TRANSACTION 或 BEGIN 开启一个事务
  • COMMIT 提交当前事务,将其永久保存下来。使其永久化。
  • ROLLBACK 回滚当前事务,取消其更改
  • SET autocommit 禁用或启用当前会话的自动默认提交模式(在MySQL中每个sql语句都会默认进行提交)

执行流程

关闭自动提交

MySQL中默认采用的是自动提交模式,在自动提交模式下,每个sql语句都会被当作一个事务执行提交操作。

查看自动提交状态:

关闭自动提交

  • 主动关闭

如果没有关闭自动提交,在执行增删改后,ROLLBACK是不能撤销操作的。

而如果关闭了自动提交,你需要在做出更改之后,使用COMMIT或者ROLLBACK提交或者撤销操作。(注意:自动提交关闭对DDL语言没有作用,例如:CREATE ,DROP 等操作)

  • 隐式的关闭

在开启一个事务之后,自动提交会暂时的关闭

使用 START TRANSACTION ,自动提交将保持禁用状态,直到您使用 COMMIT 或结束事务ROLLBACK 。自动提交模式然后恢复到其先前的状态。

开启事务

启动事务后把所有后继的SQL语句看作事务组成的一部分,知道提交或者回滚事务为止。

语法:

提交事务

提交事务后,事务结束,期间所作的操作将保存在数据库中,事务结束。

语法:

注意:

在MySQL中,存在一些特殊的命令,如果在事务中执行了这些命令,会马上强制执行COMMIT提交事务;DDL语句(CREATE TABLE/DROP TABLE/ALTER TABLE) LOCK TABLES语句等等。

回滚事务

回滚事务后,事务结束,放弃事务期间所做的任何修改,事务结束。

语句将显式事务或隐性事务回滚到事务的起点或事务内的某个保存点。(SAVAPOINT)

语法:

保存点

SAVAPOINT:保存点是事务中的一点。用于撤销部分事务,当事务结束时,会自动删除该事务定义的所有保存点。当执行RILLBACK时,通过指定保存点可以退回到指定的点

语法:

保存点操作

事务特性

事务的ACID特性:

  • 原子性(atomicity)
  • 一致性(consistency)
  • 隔离性(isolation)
  • 持久性(durability)

MySQL日志

日志主要包括:

  • 错误日志
  • 查询日志
  • 慢查询日志
  • 事务日志
  • 二进制日志

我们需要关注的是二进制日志(bin log)和事务日志(包括redo log和undo log)

InnoDB存储引擎提供了两种事务日志 :

  • redo log(重做日志):当服务器宿机时,重启后保持一致
  • undo log(回滚日志):回滚到之前的某一个状态

其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础

原子性(Atomic)

原子性是指事务作为一个整体,要么都做,要么都不做,如果事务中的一条sql语句执行失败,则已执行的语句会回滚,数据库退回到之前的状态。

实现原理 undo log(回滚日志)

在MySQL里数据每次修改前,都首先会把修改之前的数据作为历史保存一份到undo log 里面,数据里会记录操作该数据的事务ID,当事务执行失败或调用ROLLBACK,导致事务需要回滚,便可以利用undo log 中的信息将数据回滚到修改之前的状态.

回滚日志的分类:

  • insert undo log

代表事务在 insert 新记录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃

  • update undo log

事务在进行 update 或 delete 时产生的 undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除

持久性

持久性也成为永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其执行结果产生影响。

刷脏

InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)

刷脏引发的问题

Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时BufferPool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。解决这个问题通过redo log来解决。

实现原理:redo log

重做日志是一种基于磁盘的数据结构,用于在崩溃恢复期间纠正不完整事务写入的数据redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志。

一致性(Consistency)

事务操作成功后,数据库所处的状态和它的业务规则是一致的,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。

数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。

一致性是事务追求的最终目标:原子性、持久性、隔离性,都是为了保证数据库状态的一致性。

隔离性(lsolation)

隔离性是指事务内部的操作与其他事务是隔离的,并发执行的各个事物之间不能互相干扰。研究的是不同事务之间的相互影响。

隔离性主要考虑的最简单的读操作和写操作

隔离性的探讨,主要可以分为两个方面:

  • (一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离性
  • (一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性

事务的并发

并发:多个人同时对一个对象进行操作

并发可能导致下面三类问题:

  • 脏读
  • 可重复度
  • 幻读

脏读

当前事务A中可以读到其他事务B未提交的数据(脏数据)、

不可重复读

在事务A中先后两次读取同一个数据,两次读取的数据结构不一样。

幻读

在事务A中按照某个条件先后两次查询数据库,所得到的数据条数不同

事务的隔离级别

SQL标准中定义了四种隔离级别,并规定了每种隔离级别下上述几个问题是否存在。一般来说,隔离级别越低,系统开销越低,可支持的并发越高,但隔离性也越差

Y/N(可能/不可能)

读未提交在并发时会导致很多问题,而性能相对于其他隔离级别提高却很有限,因此使用较少。

可串行化强制事务串行,并发效率很低,只有当对数据一致性要求极高且可以接受没有并发时使

用,因此使用也较少。在大多数数据库系统中,默认的隔离级别是读已提交(如Oracle)或可重复读(后文简称RR)。InnoDB默认的隔离级别是RR

查看当前事务级别

MySQL 5

MySQL 8

设置事务隔离级别

在MySQL默认是用的隔离级别为RR,解决了脏读和不可重复读问题

解决幻读问题是利用MVCC+行锁(排他锁)和间隙锁进行解决

MVCC

MVCC全称Multi-Version Concurrency Control,即多版本的并发控制协议,MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突。

当前读

像 select lock in share mode (共享锁), select for update; update; insert; delete (排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

快照读

像不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC ,可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

MVCC 是为了实现读(快照读)-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。

MVCC实现原理

MVCC实现原理主要是依赖记录中的 3个隐式字段undo日志Read View 来实现.

隐式字段

InnoDB存储引擎在每行数据的后面添加了三个隐藏字段

  • DB_TRX_ID(6字节):表示最近一次对本记录行作修改(insert | update)的事务ID。
  • DB_ROLL_PTR(7字节):回滚指针,指向当前记录行的undo log信息。
  • DB_ROW_ID(6字节):随着新行插入而单调递增的行ID。

DB_ROW_ID:当表没有主键或唯一非空索引时,innodb就会使用这个行ID自动产生聚簇索引。如

果表有主键或唯一非空索引,聚簇索引就不会包含这个行ID了。这个DB_ROW_ID跟MVCC关系不大。

具体流程

事务A(事务ID为2)对该记录做出了修改,将Honor列内容改为"fmvp":

  1. 事务A先对该行加排他锁(也叫行级锁)(修改数据为当前读)
  2. 然后把该行数据拷贝到undo log中,作为旧版本
  3. 拷贝完毕后,修改该行的Honor为"fmvp",并且修改DB_TRX_ID为2(事务A的ID), 回滚指针指向拷贝到undo log的旧版本。(然后还会将修改后的最新数据写入redo log)
  4. 事务提交,释放排他锁(行级锁)。

Read View

Read View 是事务进行快照读操作的时候生产的读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID 。Read View 主要是用来做可见性判断的,把生成的读视图 (Read View)当作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的 DB_TRX_ID(即当前事务 ID)取出来,与系统当前其他活跃事务的 ID 去对比(由 Read View 维护)

当每个事务开启时,都会被分配一个 ID , 这个 ID 是递增的,所以最新的事务,ID 值越大

把 Read View 简单的理解成有三个全局属性

  • m_ids:一个数值列表,用于维护 Read View 生成时刻系统 正活跃的事务 ID 列表
  • up_limit_id:lower water remark,是 trx_list 列表中事务 ID 最小的 ID
  • low_limit_id: hight water mark,ReadView 生成时刻系统尚未分配的下一个事务 ID ,也就是目前已出现过的事务 ID 的最大值 + 1(是系统此刻可分配的事务 ID 的最小值)

例如:

假设当前列表中的事务id为【80,100】

则根据low_limit_id新事务的ID就应该为101

  • 如果你要访问的记录版本的事务ID为50,比当前列表最小的ID80小,那说明这个事务在之前就提交了,所以对当前活动的事务来说是可访问的。
  • 如果你要访问的记录版本的事务ID为90,发现此事务在列表id最大值和最小值之间,那就再判断一下是否在列表内,如果在那就说明此事务还未提交,所以版本不能被访问。如果不在那说明事务已经提交,所以版本可以被访问。
  • 如果你要访问的记录版本的事务ID为110,那比事务列表最大ID100都大,那说明这个版本是在ReadView生成之后才发生的,所以不能被访问。

锁(LOCK)

锁是计算机在执行多线程或者线程时用于并发访问同一共享资源是的同步机制。

MySQL中的锁实在服务器层或者引擎层实现的,为了保证了数组访问的一致性和有效性。

锁机制的基本原理可以概括为:

  • 事务在修改数据前,需要获得相应的锁
  • 获得锁之后,事务便可以修改数据
  • 该事务操作期间,这部分数据时锁定的,其他事务如果需要修改数据,需要等待当前事务提交或者回滚后释放锁

查看锁

基于MySQL 8

查看锁的事务

查看等待锁的事务

查看InnoDB的整体状态,包括锁的情况

锁机制的必要性

并发用户访问同一数据,锁机制可以避免数据不一致问题的发生

MySQL锁的分类

大致可分为下面几类

模式分类

  • 乐观锁(业务实现)
  • 悲观锁(select…for,update)

按粒度

  • 全局锁(全库逻辑备份,Flush tables with read lock)
  • 表级锁(并发低;MyISM : 不支持事务,不会出现死锁;InnoDB:当不走索引时全局扫描使用锁)
  • 页级锁(BDB引擎)
  • 行级锁(innoDB: 通过给索引上的索引项加锁来实现,最容易发生死锁)

按属性

  • 共享锁(s锁:select…lock in share mode 读锁)
  • 排他锁(X锁:select…from uptate 写锁)

按状态

  • 意向共享锁
  • 意向排他锁

按算法

  • 间隙锁(封锁索引记录中的间隔)
  • 记录锁
  • 临建锁

共享锁

又称之为读锁,简称S锁,当事务A对数据加上读锁后,其他事务只能对该数据加读锁,不能做任何修改操作,也就是不能添加写锁。只有当事务A上的读锁被释放后,其他事务才能对其添加写锁。

应用场景

共享锁主要是为了支持并发的读取数据而出现的,读取数据时,不允许其他事务对当前数据进行修改操作,从而避免”不可重读”的问题的出现。

实现方式

提交事务或者回滚事务后释放

排它锁

又称之为写锁、独占锁,排它锁,简称X锁,当事务对数据加上写锁后,其他事务既不能对该数据添加读写,也不能对该数据添加写锁,写锁与其他锁都是互斥的。只有当前数据写锁被释放后,其他事务才能对其添加写锁或者是读锁。

MySQL InnoDB引擎默认update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型。

应用场景

写锁主要是为了解决在修改数据时,不允许其他事务对当前数据进行修改和读取操作,从而可以有效避免”脏读”问题的产生。

实现方式

锁的粒度分类

  • 表级锁:开销小,加锁快,不会出现死锁,锁定力度大,发生冲突所的概率高,并发度低。
  • 行级锁:开销大,加锁慢,会出现死锁,锁定力度最小,发生锁冲突的概率最低,并发度高。
  • 页面锁:开销和加锁时间介于表锁和行锁之间,会出现死锁,锁定力度介于表和行行级锁之间,并发度一般。

MyISAM和MEMORY存储引擎采用表级锁

InnoDB支持行级锁、表级锁,默认情况采用行级锁

乐观锁

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时

候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如

何去做。

应用场景

适用于读多写少,因为如果出现大量的写操作,写冲突的可能性就会增大,业务层需要不断重试,会大大降低系统性能。

实现方式

一般使用数据版本(Version)记录机制实现,在数据库表中增加一个数字类型的 “version” 字段来实现

悲观锁

悲观锁,正如其名,具有强烈的独占和排他特性,每次去拿数据的时候都认为别人会修改,对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。

应用场景:适用于并发量不大、写入操作比较频繁、数据一致性比较高的场景。

实现方式:select…for update是MySQL提供的实现悲观锁的方式,属于排它锁。

在MySQL中使用悲观锁,必须关闭MySQL的自动提交 set autocommit=0。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴

死锁

当某组资源的两个或多个线程之间有循环相关性时,将发生死锁。

为避免死锁的发生,用户应该遵循以下原则:

  • 在所有的事务中都按同一顺序来访问各个表。尽可能利用存储过程来完成一个事务,以便能保证对。
  • 各表的访问次序都是一致的。
  • 事务应该尽量小且应尽快提交。
  • 尽量避免人工输入操作出现在事务中。
  • 尽量避免同时执行诸如【INSERT】、【UPDATE】和【DELETE】等数据修改语句。

相关推荐

Redis合集-使用benchmark性能测试

采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...

Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好

最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...

redission YYDS(redission官网)

每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...

从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道

2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...

SpringBoot系列——实战11:接口幂等性的形而上思...

欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...

Java面试题: 项目开发中的有哪些成长?该如何回答

在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...

互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?

你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...

每日一题 |10W QPS高并发限流方案设计(含真实代码)

面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...

Java面试题:服务雪崩如何解决?90%人栽了

服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...

面试题官:高并发经验有吗,并发量多少,如何回复?

一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...

瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪

“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...

PHP技能评测(php等级考试)

公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...

你的简历在HR眼里是青铜还是王者?

你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...

取消回复欢迎 发表评论: