MySql基础教程:事务基础知识
目录
一、数据库事务概述
1.什么是事务?
2.事务的ACID特性
3.为什么使用事务?
二、如何使用事务
1.示例代码
1.1转账示例
1.2自动提交模式
1.3事务的保存点
2.事务的状态
3.完整示例代码
4.IF()和IF...THEN的区别
三、事务隔离级别
1.事务隔离级别的概念
2.四种隔离级别
2.1设置不同隔离级别
3.各隔离级别的对比
3.1脏读
3.2幻读
3.3不可重复读
4.默认隔离级别
四、事务的常见分类
五、结语
一、数据库事务概述
1.什么是事务?
事务是数据库操作的最小单元,它包含一系列的操作,这些操作要么全部执行成功,要么全部失败回滚。MySQL中的事务支持确保了数据的一致性和完整性。
2.事务的ACID特性
-
A - 原子性(Atomicity): 事务是最小单位,不可再分。要么全部成功,要么全部失败。
-
C - 一致性(Consistency): 事务执行前后,数据库必须从一个合法状态转换到另一个合法状态。遵守所有预定义的约束(主键、外键、唯一、CHECK等)。
-
I - 隔离性(Isolation): 多个并发事务执行时,彼此的操作应该是隔离的,互不干扰。防止脏读、不可重复读、幻读。主要通过锁机制或MVCC(多版本并发控制) 实现(MySQL InnoDB默认实现MVCC)。
-
D - 持久性(Durability): 一旦事务提交(
COMMIT
),它对数据库的改变就是永久性的,即使系统故障也能恢复(通过Redo Log)。
3.为什么使用事务?
-
保障复杂操作的数据正确性(如转账、订单库存)。
-
应对系统故障(宕机、断电)。
-
支持并发操作,平衡性能与数据准确性。
-
对比: 没有事务的数据库(如某些简单KV存储或关闭事务的表引擎如MyISAM)在复杂操作或并发下容易数据混乱。MySQL的MyISAM引擎不支持事务,InnoDB引擎支持。
二、如何使用事务
1.示例代码
1.1转账示例
假设我们有两个账户account_a和account_b,我们需要将100元从account_a转移到account_b。
-- 开始事务
START TRANSACTION;-- 减少 account_a 的余额
UPDATE accounts SET balance = balance - 100 WHERE id = 'account_a';-- 增加 account_b 的余额
UPDATE accounts SET balance = balance + 100 WHERE id = 'account_b';-- 提交事务
COMMIT;
如果在执行过程中发生错误,可以使用ROLLBACK回滚事务:
-- 发生错误时回滚
ROLLBACK;
1.2自动提交模式
MySQL默认是自动提交模式(autocommit=1),即每条SQL语句都会自动提交。可以通过以下命令关闭自动提交模式:
SET autocommit = 0;
1.3事务的保存点
事务的保存点允许你在事务中设置一个标记,以便在需要时回滚到该标记而不是整个事务。
-- 设置保存点
SAVEPOINT savepoint_name;-- 回滚到保存点
ROLLBACK TO SAVEPOINT savepoint_name;-- 释放保存点
RELEASE SAVEPOINT savepoint_name;
2.事务的状态
MySQL事务在其生命周期中会经历多个状态。
- 活动的(active)
事务中的数据库操作正在执行中,此时事务的状态就是活动的。
- 部分提交的(partially committed)
事务中的最后一个操作执行完毕(在内存中执行),对数据库的操作还没有刷新(同步)到磁盘,此时事务的状态是部分提交的。
- 失败的(failed)
当事务处在活跃的或者部分提交的状态左右时,可能遇到了某些错误或者人为的停止当前事务,此时事务的状态是失败的。
- 中止的(aborted)
事务失败之后,需要撤销失败事务对当前数据库造成的影响,即回滚,当回滚操作执行完毕数据库恢复到了执行事务之前的状态,此时事务的状态是中止的。
- 提交的(committed)
当一个事务是部分提交的状态,并将对数据库的操作同步到磁盘之后,此时事务的状态是提交的。
事务的五大状态总结如下:
3.完整示例代码
事务结合存储过程实现转账示例如下:
DELIMITER $$
--创建存储过程
CREATE PROCEDURE safe_transfer(IN from_id INT, --入参IN to_id INT,IN amount DECIMAL(10,2)
)
BEGIN-- 声明变量DECLARE retry_count INT DEFAULT 0;-- mysql的捕捉sqlException机制--开始-- 可以捕捉存储过程中出现的异常DECLARE EXIT HANDLER FOR SQLEXCEPTIONBEGIN-- 出错后判断是否已经重试过IF retry_count = 0 THEN-- 第一次出错,回滚到 savepoint_startROLLBACK TO savepoint_start;-- 错误次数+1SET retry_count = retry_count + 1;-- 重新尝试执行操作CALL attempt_transfer(from_id, to_id, amount);ELSE-- 第二次失败,直接回滚整个事务ROLLBACK;END IF;END;-- mysql的捕捉sqlException机制--结束-- 开始事务START TRANSACTION;-- 设置初始保存点SAVEPOINT savepoint_start;-- 第一次尝试执行转账CALL attempt_transfer(from_id, to_id, amount);-- 提交事务COMMIT;END$$-- 辅助存储过程:实际执行转账操作
CREATE PROCEDURE attempt_transfer(IN from_id INT,IN to_id INT,IN amount DECIMAL(10,2)
)
BEGIN-- 扣款UPDATE accounts SET balance = balance - amount WHERE id = from_id;-- ROW_COUNT() 返回上一条 SQL 语句影响的行数。IF ROW_COUNT() = 0 THENSIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '扣款失败,账户不存在或余额不足';END IF;-- 加款UPDATE accounts SET balance = balance + amount WHERE id = to_id;IF ROW_COUNT() = 0 THENSIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '加款失败,目标账户不存在';END IF;
END$$DELIMITER ;
4.IF()和IF...THEN的区别
IF()是mysql的一个表达式,是一个函数。
-- 如果sex = 0 则转成'男',否则转成'女'
SELECT IF(sex = 0, '男', '女') AS gender FROM users;
在事务中示例:
-- 在 UPDATE 中使用 IF()
START TRANSACTION;-- 如果余额大于 100,则扣款 100,否则不操作
UPDATE accounts
SET balance = balance - IF(balance > 100, 100, 0)
WHERE id = 1;COMMIT;-- 在 SELECT 查询中做字段转换
START TRANSACTION;SELECT id, name, IF(balance > 0, '有余额', '无余额') AS status
FROM accounts
WHERE id IN (1, 2);COMMIT;
但你不能在事务中使用 IF ... THEN 做逻辑分支控制
例如下面这样就会报错。
START TRANSACTION;IF (SELECT balance FROM accounts WHERE id = 1) > 100 THENUPDATE accounts SET balance = balance - 100 WHERE id = 1;UPDATE accounts SET balance = balance + 100 WHERE id = 2;
ELSESELECT '余额不足' AS error;
END IF;COMMIT;
流程控制语句 IF ... THEN ... END IF;只能使用在存储过程、函数或触发器内部,不允许在“顶层”sql中直接使用。
三、事务隔离级别
1.事务隔离级别的概念
MySQL 是客户端/服务器架构,支持多个客户端同时连接,每个连接称为一个 会话(Session)。每个会话可独立发起请求,可能属于某个事务。
为了保证数据一致性,事务具有 隔离性:理论上,一个事务访问某条数据时,其他事务应排队等待,直到该事务提交。
但这种方式性能差,尤其在高并发场景下。因此,数据库需要在隔离性与并发性能之间做权衡,通过不同的 事务隔离级别 来控制并发访问的行为。
事务隔离级别决定了事务之间可见性的程度。不同的隔离级别会影响并发性能和数据一致性。
2.四种隔离级别
2.1设置不同隔离级别
-- 查看当前隔离级别
SELECT @@tx_isolation;-- 设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL isolation_level;-- 设置会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL isolation_level;--其中isolation_level可以是以下值之一:
-- READ UNCOMMITTED 读未提交
-- READ COMMITTED 读已提交
-- REPEATABLE READ 可重复度
-- SERIALIZABLE 串行化
3.各隔离级别的对比
这里简单解释一下脏读、不可重复读、幻读。
3.1脏读
含义:一个事务读取了另一个未提交的事务写入但尚未提交的数据。
问题:如果那个事务回滚,你读到的就是“垃圾数据”或“无效数据”。
3.2幻读
含义:同一个事务中,多次执行相同的查询语句,返回的行数不同(因为其他事务插入或删除了符合条件的数据)。
问题:看起来像是“凭空多了一行”或者“少了一行”,像“幻觉”一样。
3.3不可重复读
含义:同一个事务中,多次读取同一行数据,结果却不一样(因为其他事务修改并提交了该行)。
问题:数据在事务执行期间被其他事务更新并提交,导致当前事务两次读取的结果不一致。
4.默认隔离级别
MySQL支持事务的引擎只有InnoDB,默认的隔离级别是可重复读REPEATABLE-READ,这个隔离级别可以避免脏读、幻读,默认情况下通过Next-Key Lock 机制也防止了幻读。
(注:一般情况下很少遇到会修改默认隔离级别的情况。)
四、事务的常见分类
从事务理论的角度来看,可以把事务分为以下几种类型:
-
扁平事务(Flat Transactions)
-
带有保存点的扁平事务(Flat Transactions with Savepoints)
-
链事务(Chained Transactions)
-
嵌套事务(Nested Transactions)
-
分布式事务(Distributed Transactions
五、结语
MySQL事务是数据库操作的核心机制,确保数据的一致性和完整性。通过ACID特性(原子性、一致性、隔离性、持久性),事务提供了一种可靠的方式来处理复杂的数据库操作。
事务的使用场景广泛,从简单的转账操作到复杂的多表数据更新,都能通过事务确保操作的可靠性。合理设置隔离级别可以平衡性能和数据一致性,避免脏读、不可重复读和幻读等问题。
掌握事务的基本命令(如BEGIN
、COMMIT
、ROLLBACK
)以及锁机制,有助于优化数据库性能并减少并发冲突。在实际开发中,应根据业务需求选择合适的事务策略,确保系统的高效与稳定。