数据库(MySQL)—— 事务

ops/2024/9/24 5:50:27/

数据库(MySQL)—— 事务

  • 什么是事务
  • 事务操作
    • 未控制事务
    • 测试异常情况
  • 控制事务一
    • 查看/设置事务提交方式:
    • 提交事务
    • 回滚事务
  • 控制事务二
    • 开启事务
    • 提交事务
    • 回滚事务
  • 并发事务问题
    • 脏读(Dirty Read)
    • 不可重复读(Non-Repeatable Read)
    • 幻读(Phantom Read)
    • 丢失更新(Lost Update)
  • 并发事务问题演示及事务隔离级别

我们今天来学习MySQL中的事务:

什么是事务

MySQL中的事务(Transaction)是一个逻辑上不可分割的工作单元,由一系列数据库操作组成,这些操作要么全部成功执行,要么全部不执行。事务的主要目的是为了维护数据库的一致性和完整性,尤其是在多用户同时访问和修改数据的并发环境下。

事务具备以下四个关键特性,即ACID特性:

  1. 原子性(Atomicity):事务是一个原子操作,事务中的所有操作要么全部完成,要么完全不起作用。如果事务中任何一部分操作失败,整个事务都会被回滚,仿佛从未发生过。
  2. 一致性(Consistency):事务执行前后,数据库从一种合法的状态转换到另一种合法的状态。即使在事务执行期间出现错误,数据库也应该保持一致的状态,不因事务失败而残留部分结果。
  3. 隔离性(Isolation):并发执行的事务之间互不干扰,一个事务内部的操作对其他事务是不可见的,直到该事务完成并提交。MySQL通过不同的事务隔离级别来实现这一特性,如读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)。
  4. 持久性(Durability):一旦事务被提交,它对数据库的更改应该是永久性的,即使系统发生故障也不会丢失。这意味着事务的结果会被稳定地存储在数据库中。

在MySQL中,特别是使用InnoDB存储引擎时,可以通过BEGIN或START TRANSACTION语句显式地开始一个事务,使用COMMIT语句提交事务,以确认所有更改,或者使用ROLLBACK语句回滚事务,取消所有在事务中做出的更改。此外,还可以通过设置事务的隔离级别来调整不同事务之间的可见性,以适应不同的应用场景需求。

举个例子:
张三给李四转账1000块钱,张三银行账户的钱减少1000,而李四银行账户的钱要增加1000。 这一组操作就必须在一个事务的范围内,要么都成功,要么都失败。
在这里插入图片描述正常情况: 转账这个操作, 需要分为以下这么三步来完成 , 三步完成之后, 张三减少1000, 而李四增加1000, 转账成功:
在这里插入图片描述异常情况: 转账这个操作, 也是分为以下这么三步来完成 , 在执行第三步是报错了, 这样就导致张三减少1000块钱, 而李四的金额没变, 这样就造成了数据的不一致, 就出现问题了。
在这里插入图片描述为了解决上述的问题,就需要通过数据的事务来完成,我们只需要在业务逻辑执行之前开启事务,执行完毕后提交事务。如果执行过程中报错,则回滚事务,把数据恢复到事务开始之前的状态:
在这里插入图片描述

注意: 默认MySQL的事务是自动提交的,也就是说,当执行完一条DML语句时,MySQL会立即隐式的提交事务。

事务操作

数据准备:

drop table if exists account;create table account(
id int primary key AUTO_INCREMENT comment 'ID',
name varchar(10) comment '姓名',
money double(10,2) comment '余额'
) comment '账户表';insert into account(name, money) VALUES ('张三',2000), ('李四',2000);

未控制事务

测试正常情况:

-- 1. 查询张三余额
select * from account where name = '张三';
-- 2. 张三的余额减少1000
update account set money = money - 1000 where name = '张三';
-- 3. 李四的余额增加1000
update account set money = money + 1000 where name = '李四';

在这里插入图片描述

测试异常情况

我们把数据都恢复到2000:

update account set money = 2000;

然后再次一次性执行下面的SQL语句:

-- 1. 查询张三余额
select * from account where name = '张三';
-- 2. 张三的余额减少1000
update account set money = money - 1000 where name = '张三';
出错了....
-- 3. 李四的余额增加1000
update account set money = money + 1000 where name = '李四';

在这里插入图片描述
(“出错了…” 这句话不符合SQL语法,执行就会报错)

检查最终的数据情况, 发现数据在操作前后不一致了:
在这里插入图片描述

控制事务一

查看/设置事务提交方式:

SELECT @@autocommit ;
SET @@autocommit = 0 ;

在MySQL中,SELECT @@autocommit;SET @@autocommit = 0; 是用来查看和设置当前会话的自动提交状态的命令。

  • SELECT @@autocommit;
    这条命令用于查询当前会话的自动提交(autocommit)设置。@@autocommit是一个系统变量,表示是否启用自动提交模式。如果返回值为1,意味着自动提交是开启的,即每次执行完一条SQL语句后,改动会立即保存到数据库中,成为永久性的变更。如果返回值为0,则表示自动提交是关闭的,你需要手动控制事务的提交与回滚。
    在这里插入图片描述
  • SET @@autocommit = 0;
    这条命令用于设置当前会话的自动提交模式为关闭状态。将@@autocommit的值设为0后,意味着在该会话中执行的SQL语句不会自动提交,直到你显式执行COMMIT命令才将事务中的更改永久保存。这样可以让你在一系列操作之间保持数据的一致性,便于实现事务的原子性、一致性、隔离性和持久性(ACID特性)。在需要手动控制事务边界的应用场景中非常有用。
    在这里插入图片描述

提交事务

此时我们又把数据恢复到2000,又执行那三条语句:
在这里插入图片描述我们查看结果发生变化了,但是这只是针对当前会话,如果我们重新开一个会话,就会发现数据没有发生变化,这里我用Navicat重开一个会话:
在这里插入图片描述
但是如果我执行commit,提交之后:
在这里插入图片描述
再在Navicat中查询结果:
在这里插入图片描述

所以我们设置提交模式为非自动的时候,如果我们想要我们的提交彻底生效,要记得执行commit

回滚事务

如果我在事务的流程当中出错了:
在这里插入图片描述
可以执行ROLLBACK操作,使数据回到开始前的状态:
在这里插入图片描述

控制事务二

开启事务

START TRANSACTIONBEGIN ; 1

提交事务

COMMIT;

回滚事务

ROLLBACK;

举个例子:

-- 开启事务
start transaction;-- 1. 查询张三余额
select * from account where name = '张三';
-- 2. 张三的余额减少1000
update account set money = money - 1000 where name = '张三';
-- 3. 李四的余额增加1000
update account set money = money + 1000 where name = '李四';
-- 如果正常执行完毕, 则提交事务
commit;-- 如果执行过程中报错, 则回滚事务
-- rollback;

在这里插入图片描述

并发事务问题

并发事务问题是数据库管理系统中常见的挑战,特别是在多用户环境下,不同的事务可能同时对相同的数据进行读取或修改,从而引发以下几种典型的问题:

  1. 脏读(Dirty Read):事务A读取了事务B尚未提交的数据,如果事务B随后进行了回滚,那么事务A读取到的就是无效的“脏”数据。
  2. 不可重复读(Non-Repeatable Read):在同一个事务内,两次相同的查询返回了不同的结果,这是因为在两次查询之间,有其他事务提交了对该数据的修改或删除操作。
  3. 幻读(Phantom Read):在一个事务内,第一次查询时没有特定条件的某些行,但是在同一事务内的第二次查询时,由于其他事务插入了新行,这些新行就仿佛“幻影”一样出现了。
  4. 丢失更新(Lost Update):两个事务同时读取同一条数据,然后基于原始数据进行修改并提交,如果数据库系统没有适当的并发控制机制,后提交的事务可能会覆盖前一个事务的更改,导致前一个事务的更新丢失。

脏读(Dirty Read)

场景:事务A正在更新一条记录但尚未提交,事务B在此时读取了事务A更改但未提交的数据。

  • 事务A:

    BEGIN;
    UPDATE accounts SET salary = salary - 100 WHERE id = 1; -- 减少张三账户100元,但没提交
    
  • 事务B (同时进行):

    SELECT salary FROM accounts WHERE id = 1; -- 查询张三账户余额,看到减少了100元
    
  • 假设:事务A因为某种原因执行了ROLLBACK。

  • 结果:事务B读取到了实际上未生效的“脏”数据(减少了100元的余额),而最终张三的账户余额并未发生变化。

不可重复读(Non-Repeatable Read)

场景:在同一个事务内,两次查询返回了不同的结果,因为中间有其他事务提交了修改。

  • 事务A:

    BEGIN;
    SELECT salary FROM accounts WHERE id = 1; -- 第一次查询张三余额为500元
    
  • 事务B (并发执行,并提交):

    UPDATE accounts SET salary = salary + 100 WHERE id = 1; -- 给张三账户加100元并提交
    
  • 事务A 继续:

    SELECT salary FROM accounts WHERE id = 1; -- 第二次查询张三余额变为600元
    
  • 结果:事务A在同一个事务内两次查询同一数据得到不同结果,违反了可重复读原则。

幻读(Phantom Read)

场景:在一个事务内,两次相同的查询因其他事务插入新行而返回不同的行数。

  • 事务A:

    BEGIN;
    SELECT * FROM orders WHERE customer_id = 1; -- 初始查询客户1的订单有5个
    
  • 事务B (并发执行,并提交):

    INSERT INTO orders(customer_id, order_date) VALUES(1, CURDATE()); -- 给客户1新增一个订单并提交
    
  • 事务A 继续:

    SELECT * FROM orders WHERE customer_id = 1; -- 再次查询客户1的订单,现在有6个
    
  • 结果:事务A第二次查询时出现了额外的“幻影”行,即原本不存在但在查询过程中被其他事务插入的记录。

丢失更新(Lost Update)

场景:两个事务同时修改同一数据,后提交的事务覆盖了前一个事务的更改。

  • 事务A:

    BEGIN;
    SELECT salary FROM accounts WHERE id = 1; -- 张三余额为500元
    
  • 事务B (几乎同时开始,并提交快):

    BEGIN;
    SELECT salary FROM accounts WHERE id = 1; -- 也看到张三余额为500元
    UPDATE accounts SET salary = salary + 100 WHERE id = 1; -- 增加100元并提交
    
  • 事务A 继续, 未知事务B已更新:

    UPDATE accounts SET salary = salary - 100 WHERE id = 1; -- 减少100元并提交,基于旧值操作
    
  • 结果:尽管事务B给张三增加了100元,但事务A的减操作基于事务B之前的数据,最终张三的账户余额没有变化,事务B的更新被事务A无意中“丢失”了。

为了解决这些问题,数据库系统提供了不同的事务隔离级别,分别是:

  • 读未提交(Read Uncommitted):最低的隔离级别,允许脏读、不可重复读和幻读。
  • 读已提交(Read Committed):只允许不可重复读和幻读,禁止脏读。
  • 可重复读(Repeatable Read):MySQL的InnoDB默认隔离级别,禁止脏读和不可重复读,但可能出现幻读。
  • 串行化(Serializable):最高的隔离级别,通过完全锁定读取和更新的行,避免了脏读、不可重复读和幻读,但可能导致大量锁竞争,影响性能。

并发事务问题演示及事务隔离级别

在这里插入图片描述图中打钩的表示在此模式下,该问题可能发生,打叉表示可防止此问题发生。

SELECT @@TRANSACTION_ISOLATION;

在这里插入图片描述
这里我们要看脏读,首先要把事务的隔离级别设置为Read uncommitted,设置事务隔离级别用下面的代码:

SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED |
READ COMMITTED | REPEATABLE READ | SERIALIZABLE }

session是设置当前对话,global是全局。


http://www.ppmy.cn/ops/32447.html

相关文章

笔记85:如何计算递归算法的“时间复杂度”和空间复杂度?

先上公式: 递归算法的时间复杂度 递归次数 x 每次递归消耗的时间颗粒数递归算法的空间复杂度 递归深度 x 每次递归消耗的内存空间大小 注意: 时间复杂度指的是在执行这一段程序的时候,所花费的全部的时间,即时间的总和而空间复…

Unity---版本控制软件

13.3 版本控制——Git-1_哔哩哔哩_bilibili Git用的比较多 Git 常用Linux命令 pwd:显示当前所在路径 ls:显示当前路径下的所有文件 tab键自动补全 cd:切换路径 mkdir:在当前路径下创建一个文件夹 clear:清屏 vim…

全方位解析Node.js:从模块系统、文件操作、事件循环、异步编程、性能优化、网络编程等高级开发到后端服务架构最佳实践以及Serverless服务部署指南

Node.js是一种基于Chrome V8引擎的JavaScript运行环境,专为构建高性能、可扩展的网络应用而设计。其重要性在于革新了后端开发,通过非阻塞I/O和事件驱动模型,实现了轻量级、高并发处理能力。Node.js的模块化体系和活跃的npm生态极大加速了开发…

Octave行列式矩阵运算

Octave行列式矩阵运算 Octave计算行列式指令一步步计算行列式 Octave矩阵加法Octave矩阵乘法Octave矩阵转置Octave矩阵求秩Octave矩阵求逆 仅供本人查阅 Octave 是一个开源的数值计算软件,主要用于数学计算、算法开发和数据可视化。它是 MATLAB 语言的一个兼容性很高…

wpf线程中更新UI的4种方式

在wpf中,更新UI上面的数据,那是必经之路,搞不好,就是死锁,或者没反应,很多时候,都是嵌套的非常深导致的。但是更新UI的方式,有很多的种,不同的方式,表示的意思…

一毛钱不到的FH8208C单节锂离子和锂聚合物电池一体保护芯片

前言 目前市场上电池保护板,多为分体方案,多数场合使用没有问题,部分场合对空间有进一步要求,或者你不想用那么多器件,想精简一些,那么这个芯片就很合适,对于充电电池来说,应在使用…

目标跟踪—卡尔曼滤波

目标跟踪—卡尔曼滤波 卡尔曼滤波引入 滤波是将信号中特定波段频率滤除的操作,是抑制和防止干扰的一项重要措施。是根据观察某一随机过程的结果,对另一与之有关的随机过程进行估计的概率理论与方法。 历史上最早考虑的是维纳滤波,后来R.E.卡…

EXCEL怎样把筛选后含有公式的数据,复制粘贴到同一行的其它列?

自excel2003版之后,常规情况下,复制筛选后的数据,会忽略隐藏行,仅复制其筛选后的数据,粘贴则是粘贴到连续单元格区域,不管行是在显示状态还是隐藏状态。 一、初始数据: 二、题主的复制粘贴问题…