数据库(MySQL)—— 事务

我们今天来学习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是全局。

  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值