目录
1 事务的概念
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
2 MySQL 事务
MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!
- 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
- 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
- 事务用来管理 insert,update,delete 语句
一般来说,事务是必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
-
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
-
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
-
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
-
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。
3 事务控制语句:
-
BEGIN 或 START TRANSACTION 显式地开启一个事务;
-
COMMIT 也可以使用 COMMIT WORK,不过二者是等价的。COMMIT 会提交事务,并使已对数据库进行的所有修改成为永久性的;
-
ROLLBACK 也可以使用 ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
-
SAVEPOINT identifier,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;
-
RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
-
ROLLBACK TO identifier 把事务回滚到标记点;
-
SET TRANSACTION 用来设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。
4 MYSQL 事务处理主要有两种方法
1、用 BEGIN, ROLLBACK, COMMIT来实现
- BEGIN 开始一个事务
- ROLLBACK 事务回滚
- COMMIT 事务确认
2、直接用 SET 来改变 MySQL 的自动提交模式:
- SET AUTOCOMMIT=0 禁止自动提交
- SET AUTOCOMMIT=1 开启自动提交
5 测试代码
mysql> use RUNOOB;
Database changed
mysql> CREATE TABLE runoob_transaction_test( id int(5)) engine=innodb; # 创建数据表
Query OK, 0 rows affected (0.04 sec)
mysql> select * from runoob_transaction_test;
Empty set (0.01 sec)
mysql> begin; # 开始事务
Query OK, 0 rows affected (0.00 sec)
mysql> insert into runoob_transaction_test value(5);
Query OK, 1 rows affected (0.01 sec)
mysql> insert into runoob_transaction_test value(6);
Query OK, 1 rows affected (0.00 sec)
mysql> commit; # 提交事务
Query OK, 0 rows affected (0.01 sec)
mysql> select * from runoob_transaction_test;
+------+
| id |
+------+
| 5 |
| 6 |
+------+
2 rows in set (0.01 sec)
mysql> begin; # 开始事务
Query OK, 0 rows affected (0.00 sec)
mysql> insert into runoob_transaction_test values(7);
Query OK, 1 rows affected (0.00 sec)
mysql> rollback; # 回滚
Query OK, 0 rows affected (0.00 sec)
mysql> select * from runoob_transaction_test; # 因为回滚所以数据没有插入
+------+
| id |
+------+
| 5 |
| 6 |
+------+
2 rows in set (0.01 sec)
mysql>
6 MySQL事务实践
- 一条或多条 SQL 语句组成一个执行单元,其特点是这个单元要么同时成功要么同时失败,单元中的每条 SQL 语句都相互依赖,形成一个整体,如果某条 SQL 语句执行失败或者出现错误,那么整个单元就会回滚,撤回到事务最初的状态,如果单元中所有的 SQL 语句都执行成功,则事务就顺利执行。
-- 创建db10数据库
CREATE DATABASE db10;
-- 使用db10数据库
USE db10;
-- 创建账户表
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT, -- 账户id
NAME VARCHAR(20), -- 账户名称
money DOUBLE -- 账户余额
);
-- 添加数据
INSERT INTO account VALUES (NULL,'张三',1000),(NULL,'李四',1000);
-- 张三给李四转账500元
-- 1.张三账户-500
UPDATE account SET money=money-500 WHERE NAME='张三';
-- 2.李四账户+500
出错了...
UPDATE account SET money=money+500 WHERE NAME='李四';
-- 该场景下,这两条sql语句要么同时成功,要么同时失败。就需要被事务所管理!
- 操作事务的三个步骤
- 开启事务:记录回滚点,并通知服务器,将要执行一组操作,要么同时成功、要么同时失败
- 执行sql语句:执行具体的一条或多条sql语句
- 结束事务(提交|回滚)
- 提交:没出现问题,数据进行更新
- 回滚:出现问题,数据恢复到开启事务时的状态
- 开启事务
-- 标准语法
START TRANSACTION;
- 回滚事务
-- 标准语法
ROLLBACK;
- 提交事务
-- 标准语法
COMMIT;
- 管理事务演示
-- 开启事务
START TRANSACTION;
-- 张三给李四转账500元
-- 1.张三账户-500
UPDATE account SET money=money-500 WHERE NAME='张三';
-- 2.李四账户+500
-- 出错了...
UPDATE account SET money=money+500 WHERE NAME='李四';
-- 回滚事务(出现问题)
ROLLBACK;
-- 提交事务(没出现问题)
COMMIT;
-
提交方式
- 自动提交(MySQL默认为自动提交)
- 手动提交
-
修改提交方式
- 查看提交方式
-- 标准语法 SELECT @@AUTOCOMMIT; -- 1代表自动提交 0代表手动提交
- 修改提交方式
-- 标准语法 SET @@AUTOCOMMIT=数字; -- 修改为手动提交 SET @@AUTOCOMMIT=0; -- 查看提交方式 SELECT @@AUTOCOMMIT;
- 原子性(atomicity)
- 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响
- 一致性(consistency)
- 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态
- 拿转账来说,假设张三和李四两者的钱加起来一共是2000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是2000,这就是事务的一致性
- 隔离性(isolcation)
- 隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
- 持久性(durability)
- 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
- 隔离级别的概念
- 多个客户端操作时 ,各个客户端的事务之间应该是隔离的,相互独立的 , 不受影响的。
- 而如果多个事务操作同一批数据时,则需要设置不同的隔离级别 , 否则就会产生问题 。
- 我们先来了解一下四种隔离级别的名称 , 再来看看可能出现的问题
- 四种隔离级别
1 | 读未提交 | read uncommitted |
---|---|---|
2 | 读已提交 | read committed |
3 | 可重复读 | repeatable read |
4 | 串行化 | serializable |
- 可能引发的问题
问题 | 现象 |
---|---|
脏读 | 是指在一个事务处理过程中读取了另一个未提交的事务中的数据 , 导致两次查询结果不一致 |
不可重复读 | 是指在一个事务处理过程中读取了另一个事务中修改并已提交的数据, 导致两次查询结果不一致 |
幻读 | select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入。或不存在执行delete删除,却发现删除成功 |
- 查询数据库隔离级别
-- 标准语法
SELECT @@TX_ISOLATION;
- 修改数据库隔离级别
-- 标准语法
SET GLOBAL TRANSACTION ISOLATION LEVEL 级别字符串;
-- 修改数据库隔离级别为read uncommitted
SET GLOBAL TRANSACTION ISOLATION LEVEL read uncommitted;
-- 查看隔离级别
SELECT @@TX_ISOLATION; -- 修改后需要断开连接重新开
-
脏读的问题
- 窗口1
-- 查询账户表 select * from account; -- 设置隔离级别为read uncommitted set global transaction isolation level read uncommitted; -- 开启事务 start transaction; -- 转账 update account set money = money - 500 where id = 1; update account set money = money + 500 where id = 2; -- 窗口2查询转账结果 ,出现脏读(查询到其他事务未提交的数据) -- 窗口2查看转账结果后,执行回滚 rollback;
- 窗口2
-- 查询隔离级别 select @@tx_isolation; -- 开启事务 start transaction; -- 查询账户表 select * from account;
-
解决脏读的问题和演示不可重复读的问题
- 窗口1
-- 设置隔离级别为read committed set global transaction isolation level read committed; -- 开启事务 start transaction; -- 转账 update account set money = money - 500 where id = 1; update account set money = money + 500 where id = 2; -- 窗口2查看转账结果,并没有发生变化(脏读问题被解决了) -- 执行提交事务。 commit; -- 窗口2查看转账结果,数据发生了变化(出现了不可重复读的问题,读取到其他事务已提交的数据)
- 窗口2
-- 查询隔离级别 select @@tx_isolation; -- 开启事务 start transaction; -- 查询账户表 select * from account;
-
解决不可重复读的问题
- 窗口1
-- 设置隔离级别为repeatable read set global transaction isolation level repeatable read; -- 开启事务 start transaction; -- 转账 update account set money = money - 500 where id = 1; update account set money = money + 500 where id = 2; -- 窗口2查看转账结果,并没有发生变化 -- 执行提交事务 commit; -- 这个时候窗口2只要还在上次事务中,看到的结果都是相同的。只有窗口2结束事务,才能看到变化(不可重复读的问题被解决)
- 窗口2
-- 查询隔离级别 select @@tx_isolation; -- 开启事务 start transaction; -- 查询账户表 select * from account; -- 提交事务 commit; -- 查询账户表 select * from account;
-
幻读的问题和解决
- 窗口1
-- 设置隔离级别为repeatable read set global transaction isolation level repeatable read; -- 开启事务 start transaction; -- 添加一条记录 INSERT INTO account VALUES (3,'王五',1500); -- 查询账户表,本窗口可以查看到id为3的结果 SELECT * FROM account; -- 提交事务 COMMIT;
- 窗口2
-- 查询隔离级别 select @@tx_isolation; -- 开启事务 start transaction; -- 查询账户表,查询不到新添加的id为3的记录 select * from account; -- 添加id为3的一条数据,发现添加失败。出现了幻读 INSERT INTO account VALUES (3,'测试',200); -- 提交事务 COMMIT; -- 查询账户表,查询到了新添加的id为3的记录 select * from account;
- 解决幻读的问题
/* 窗口1 */ -- 设置隔离级别为serializable set global transaction isolation level serializable; -- 开启事务 start transaction; -- 添加一条记录 INSERT INTO account VALUES (4,'赵六',1600); -- 查询账户表,本窗口可以查看到id为4的结果 SELECT * FROM account; -- 提交事务 COMMIT; /* 窗口2 */ -- 查询隔离级别 select @@tx_isolation; -- 开启事务 start transaction; -- 查询账户表,发现查询语句无法执行,数据表被锁住!只有窗口1提交事务后,才可以继续操作 select * from account; -- 添加id为4的一条数据,发现已经存在了,就不会再添加了!幻读的问题被解决 INSERT INTO account VALUES (4,'测试',200); -- 提交事务 COMMIT;
隔离级别 | 名称 | 出现脏读 | 出现不可重复读 | 出现幻读 | 数据库默认隔离级别 | |
---|---|---|---|---|---|---|
1 | read uncommitted | 读未提交 | 是 | 是 | 是 | |
2 | read committed | 读已提交 | 否 | 是 | 是 | Oracle / SQL Server |
3 | repeatable read | 可重复读 | 否 | 否 | 是 | MySQL |
4 | **serializable ** | 串行化 | 否 | 否 | 否 |
注意:隔离级别从小到大安全性越来越高,但是效率越来越低 , 所以不建议使用READ UNCOMMITTED 和 SERIALIZABLE 隔离级别.
- 一条或多条 SQL 语句组成一个执行单元,其特点是这个单元要么同时成功要么同时失败。例如转账操作
- 开启事务:start transaction;
- 回滚事务:rollback;
- 提交事务:commit;
- 事务四大特征
- 原子性
- 持久性
- 隔离性
- 一致性
- 事务的隔离级别
- read uncommitted(读未提交)
- read committed (读已提交)
- repeatable read (可重复读)
- serializable (串行化)