MySQL数据库基础 === 事务

53 阅读15分钟

MySQL数据库基础 === 事务

目录

[TOC]

在当今数字化时代,数据库作为信息存储的核心,其数据的准确性和完整性至关重要。MySQL作为广泛使用的开源关系型数据库管理系统,其事务机制为数据库操作提供了强大的可靠性保障。本文将深入探讨MySQL事务的相关知识,包括其概念、特性、类型、隔离级别以及实际应用中的注意事项,帮助读者更好地理解和运用MySQL事务。


准备数据

CREATE TABLE account (
	id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
	NAME VARCHAR ( 10 ) COMMENT '姓名',
	money INT COMMENT '余额' 
) COMMENT = '账户表';
-- 插入初始数据
INSERT INTO account ( NAME, money )
VALUES
	( '张三', 2000 ),
	( '李四', 2000 );
-- 恢复数据
UPDATE account SET money = 2000 WHERE name = "张三" OR name="李四";

一、MySQL事务的概念

事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。这些操作要么全部成功执行,要么全部不执行,从而保证数据库从一个一致性状态转换到另一个一致性状态。例如,在银行转账业务中,从账户A向账户B转账1000元,需要执行两个操作:从账户A中扣除1000元,向账户B中增加1000元。这两个操作必须作为一个事务来执行,如果其中一个操作失败,如账户A扣款成功但账户B加款失败,那么整个事务应该回滚,恢复到事务开始前的状态,以确保资金的正确性。

(一)转账业务
--  转账 操作 (张三给李四转1000)
 
-- 查询张三的余额
SELECT * FROM account WHERE name = "张三";
	
-- 	张三余额 - 1000
UPDATE account SET money= money - 1000 WHERE name = "张三";
 
-- 李四余额 +1000
UPDATE account SET money= money + 1000 WHERE name = "李四";
(二)模拟异常

出现错误之后张三的钱扣了,李四的钱没加上,就出现了问题

默认mysql的事物是自动提交的,也就是说当执行一条sql语句,mysql 会立即隐式的提交数据

-- 查询张三的余额
SELECT * FROM account WHERE name = "张三";
	
-- 	张三余额 - 1000
UPDATE account SET money= money - 1000 WHERE name = "张三";
 
 
 模拟错误
-- 李四余额 +1000
UPDATE account SET money= money + 1000 WHERE name = "李四";
(三)控制事物 方式一(修改提交方式)

-- 查看事务提交方式  SELECT @@AUTOCOMMIT;
-- 设置事务提交方式,1为自动提交,0为手动提交,该设置只对当前会话有效
SET @@AUTOCOMMIT = 0;
-- 提交事务  COMMIT;
-- 回滚事务  ROLLBACK;

  • 查询事物 SELECT @@AUTOCOMMIT
  • 开启事务 SET @@AUTOCOMMIT = 0;(1为自动提交,0为手动提交,该设置只对当前会话有效)
  • 提交事务 COMMIT;
-- 查询事物
SELECT @@AUTOCOMMIT
-- 1为自动提交,0为手动提交,该设置只对当前会话有效
SET @@AUTOCOMMIT = 0;
-- 查询张三的余额
SELECT * FROM account WHERE name = "张三";
-- 	张三余额 - 1000
UPDATE account SET money= money - 1000 WHERE name = "张三";
-- 李四余额 +1000
UPDATE account SET money= money + 1000 WHERE name = "李四";
-- 提交事务  COMMIT;
COMMIT;
  • 回滚事务 ROLLBACK
-- 查询张三的余额
SELECT * FROM account WHERE name = "张三";
-- 	张三余额 - 1000
UPDATE account SET money= money - 1000 WHERE name = "张三";
错误
-- 李四余额 +1000
UPDATE account SET money= money + 1000 WHERE name = "李四";
 
--  回滚事物
ROLLBACK
(四)控制事物 方式二 (操作事务)

开启事务:
START TRANSACTION 或 BEGIN TRANSACTION;
提交事务:
COMMIT;
回滚事务:
ROLLBACK;

-- 	开启事物
START TRANSACTION;
-- 	查询张三的余额
SELECT * FROM account WHERE name = "张三";
-- 	张三余额 - 1000
UPDATE account SET money= money - 1000 WHERE name = "张三";
错误
-- 李四余额 +1000
UPDATE account SET money= money + 1000 WHERE name = "李四";
-- 提交事务
COMMIT;
--  回滚事物
ROLLBACK;

二、MySQL事务的特性

MySQL事务具有四个核心特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),这四个特性共同保障了事务的可靠执行。

(一)原子性

原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。在执行事务过程中,如果遇到任何错误,如系统故障、磁盘损坏等,事务将被回滚到事务开始前的状态,所有已执行的操作都会被撤销。例如,在一个电商平台的订单创建事务中,包括插入订单记录、更新库存数量、生成支付信息等多个步骤。如果在更新库存数量时发现库存不足,整个事务将回滚,订单记录不会被插入,支付信息也不会生成,从而保证了数据库状态的一致性。

(二)一致性

一致性是指事务执行前后,数据库的完整性约束没有被破坏。事务必须使数据库从一个一致性状态转变到另一个一致性状态。这包括数据的完整性、参照完整性等。例如,在一个学生成绩管理系统中,成绩表和学生表之间存在外键约束,学生成绩必须对应一个有效的学生记录。在插入一条成绩记录时,事务会检查对应的学号是否存在于学生表中,如果不存在,则事务会失败并回滚,以保证数据库的参照完整性。

(三)隔离性

隔离性是指并发执行的事务之间彼此隔离,一个事务的执行不能被其他事务干扰。MySQL提供了多种事务隔离级别,以控制事务之间的可见性和并发性。不同的隔离级别允许不同程度的并发操作,从而在保证数据一致性的同时,提高数据库的性能。例如,在读已提交隔离级别下,一个事务只能看到其他事务提交后的数据,避免了脏读现象,但在可重复读隔离级别下,事务在执行期间看到的数据快照是不变的,即使其他事务对数据进行了修改,当前事务也不会受到影响,从而保证了事务的隔离性。

(四)持久性

持久性是指事务一旦提交,其对数据库的改变是永久性的,即使系统出现故障,这些改变也不会丢失。MySQL通过将事务的操作记录到日志文件中来实现持久性。当事务提交时,MySQL会先将事务的日志写入磁盘,然后再将事务对数据的修改写入数据文件。这样,即使在事务提交后系统发生故障,如断电、磁盘损坏等,MySQL可以通过日志文件恢复事务对数据库的修改,保证数据的持久性。

脏读、不可重复读与幻读:MySQL事务中的三大幻影

在MySQL事务处理中,存在三种常见的数据一致性问题,即脏读、不可重复读和幻读。这些现象如同数据库事务中的“幻影”,若不加以控制,将严重破坏数据的完整性和准确性。本文将详细剖析这三种问题的定义、产生原因及解决方法,助你揭开MySQL事务的神秘面纱。

一、脏读

(一)定义

脏读是指一个事务读取到另一个事务尚未提交的数据。在数据库事务的隔离级别中,如果隔离级别设置得过低,就可能出现脏读现象。例如,事务A正在对某条记录进行修改,但尚未提交,此时事务B读取了这条记录。如果事务A最终回滚,那么事务B读取到的数据就是无效的,这就是脏读。


假设有一个图书管理系统,其中有一张书籍表,记录了书籍的名称、数量等信息。现在有两个事务,事务A和事务B。

事务A执行以下操作:

  1. 开始事务。

  2. 将书籍《数据库原理》的数量从10本修改为5本(尚未提交)。

事务B执行以下操作:

  1. 开始事务。

  2. 查询书籍《数据库原理》的数量,得到结果为5本。

  3. 事务A回滚,书籍《数据库原理》的数量恢复为10本。

  4. 事务B再次查询书籍《数据库原理》的数量,得到结果为10本。

在这个过程中,事务B读取到了事务A未提交的数据(5本),这就是脏读。因为事务A最终回滚,事务B读取到的5本这个数据是无效的。

(二)产生原因

脏读产生的根本原因在于数据库事务的隔离性没有得到充分保障。在读未提交(Read Uncommitted)隔离级别下,事务可以读取其他事务未提交的数据,从而导致脏读的发生。这种隔离级别对并发事务的限制最少,因此最容易出现数据一致性问题。

(三)解决方法

要避免脏读,需要提高事务的隔离级别。在MySQL中,将隔离级别设置为读已提交(Read Committed)或更高,就可以防止脏读的发生。读已提交隔离级别下,事务只能读取到其他事务已经提交的数据,从而避免了读取到未提交的“脏数据”。

二、不可重复读

(一)定义

不可重复读是指在一个事务中,多次读取同一条记录,但读取到的数据不同。这种情况通常发生在多个事务并发执行时,一个事务在读取数据的过程中,另一个事务对数据进行了修改并提交,导致第一个事务在后续读取时得到了不同的数据。


事务A执行以下操作:

  1. 开始事务。

  2. 查询书籍《数据库原理》的数量,得到结果为10本。

  3. 等待一段时间。

事务B执行以下操作:

  1. 开始事务。

  2. 将书籍《数据库原理》的数量从10本修改为8本,并提交。

事务A继续执行: 4. 再次查询书籍《数据库原理》的数量,得到结果为8本。

在这个过程中,事务A在同一个事务中,先后两次查询同一条记录(书籍《数据库原理》的数量),但得到的结果不同(第一次为10本,第二次为8本),这就是不可重复读。因为事务B对数据进行了修改并提交,导致事务A后续读取到了不同的数据。

(二)产生原因

不可重复读产生的原因是事务之间的并发操作没有得到有效的隔离。在读已提交隔离级别下,虽然避免了脏读,但仍然可能出现不可重复读。因为在这个隔离级别下,事务可以读取到其他事务提交后的最新数据,而这些数据可能在事务读取的过程中被修改。

(三)解决方法

要解决不可重复读问题,需要进一步提高事务的隔离级别。将隔离级别设置为可重复读(Repeatable Read)可以有效避免不可重复读。在可重复读隔离级别下,事务在读取数据时,会获取数据的一致性视图,即事务开始时的数据快照。即使其他事务对数据进行了修改并提交,当前事务仍然会看到事务开始时的数据版本,从而保证了读取数据的一致性。

三、幻读

(一)定义

幻读是指一个事务按照条件查询数据时,没有找到对应的数据行,但在后续操作中,又发现这行数据已经存在。幻读通常发生在插入或删除操作中,一个事务在查询数据后,另一个事务插入了新的数据,导致第一个事务在后续操作中发现了之前不存在的数据。


还是使用图书管理系统场景。事务A和事务B同时执行。

事务A执行以下操作:

  1. 开始事务。

  2. 查询所有书籍的数量,发现没有《数据挖掘》这本书。

  3. 等待一段时间。

事务B执行以下操作:

  1. 开始事务。

  2. 插入一条新记录,书籍名称为《数据挖掘》,数量为3本,并提交。

事务A继续执行: 4. 再次查询所有书籍的数量,发现出现了《数据挖掘》这本书,数量为3本。

在这个过程中,事务A在同一个事务中,按照条件(查询所有书籍)先后两次查询数据,第一次没有找到《数据挖掘》这本书,但在后续操作中,又发现了这本书,这就是幻读。因为事务B插入了新的数据,导致事务A后续查询到了之前不存在的数据。

(二)产生原因

幻读产生的原因是事务之间的并发插入或删除操作没有得到有效的隔离。在可重复读隔离级别下,虽然避免了不可重复读,但仍然可能出现幻读。因为在这个隔离级别下,事务对数据的锁定是基于行的,而对于新插入的数据,由于之前不存在,所以不会被锁定。这就导致了其他事务可以插入新的数据,从而引发幻读。

(三)解决方法

要彻底解决幻读问题,需要将事务的隔离级别设置为串行化(Serializable)。在串行化隔离级别下,事务会以串行的方式执行,即一次只允许一个事务操作数据库,其他事务需要等待当前事务完成后才能执行。这样,可以完全避免并发事务之间的干扰,包括幻读现象。然而,串行化隔离级别也会带来性能上的损失,因为事务需要相互等待,降低了系统的并发性能。

四、MySQL事务的隔离级别

MySQL提供了四种事务隔离级别,分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。不同的隔离级别对并发事务的可见性和一致性有不同的影响。

查看事务隔离级别:
SELECT @@TRANSACTION_ISOLATION;
设置事务隔离级别:
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE };
SESSION 是会话级别,表示只针对当前会话有效,GLOBAL 表示对所有会话有效

(一)读未提交(Read Uncommitted)

这是最低的隔离级别,在这种级别下,一个事务可以看到其他事务未提交的数据。这意味着可能会出现脏读现象,即读取到其他事务正在修改但尚未提交的数据。例如,事务A正在更新一条记录,事务B在事务A提交之前读取了这条记录,如果事务A最终回滚,事务B读取到的数据就是无效的。这种隔离级别很少在实际应用中使用,因为它无法保证数据的一致性。

(二)读已提交(Read Committed)

在这种隔离级别下,一个事务只能看到其他事务提交后的数据,避免了脏读现象。但是,可能会出现不可重复读问题,即在同一个事务中,多次读取同一数据可能会得到不同的结果。例如,事务A在读取一条记录后,事务B修改并提交了这条记录,当事务A再次读取这条记录时,会得到更新后的数据。这种隔离级别适用于对数据一致性要求不是特别严格的场景,如一些统计报表的生成,允许在短时间内数据存在轻微的不一致。

(三)可重复读(Repeatable Read)

这是MySQL的默认隔离级别。在这种级别下,一个事务在执行期间看到的数据快照是不变的,即使其他事务对数据进行了修改,当前事务也不会受到影响,从而避免了不可重复读问题。但是,可能会出现幻读现象,即在同一个事务中,多次查询满足某个条件的记录数可能会得到不同的结果。例如,事务A在查询满足某个条件的记录数后,事务B插入了一些满足该条件的记录并提交,当事务A再次查询满足该条件的记录数时,会得到增加后的结果。这种隔离级别适用于大多数业务场景,能够较好地保证数据的一致性和事务的隔离性。

(四)串行化(Serializable)

这是最高的隔离级别,在这种级别下,事务会被串行执行,即一次只允许一个事务执行,其他事务需要等待当前事务完成后才能执行。这种隔离级别可以避免脏读、不可重复读和幻读问题,保证了事务的最高程度隔离。但是,它也会导致并发性能大幅下降,因为事务之间需要相互等待。这种隔离级别适用于对数据一致性要求极高且并发量不大的场景,如金融交易系统中的核心交易事务等。