事务(transaction)
概述
一个事务其实就是一个完整的业务逻辑。是一个最小的工作单元。不可再分。
- 假设从A账户向B账户中转账10000 , 将A账户的钱减去10000(update语句), 将B账户的钱加上10000(update语句), 这就是一个完整的业务逻辑。
事务就是批量的DML语句同时成功,或者同时失败
- 只有DML语句才会有事务这一说,其它语句和事务无关 , 只要你的操作一旦涉及到数据的增、删、改,那么就一定要考虑安全问题。
- 如果任何一件复杂的事儿都能一条DML语句搞定,那么事务则没有存在的价值了。但是一个业务通常需要多条DML语句共同联合起来才能完成,所以需要事务的存在。
在mysql当中默认的事务行为是怎样的?
- mysql默认情况下是支持自动提交事务的(每执行一条DML语句,则提交一次)
- 这种自动提交实际上是不符合我们的开发习惯,因为一个业务通常是需要多条DML语句共同执行才能完成的,为了保证数据的安全,必须要求同时成功之后再提交,所以不能执行一条就提交一条。
- 关闭mysql的自动提交机制: start transaction;
原理
事务是怎么做到多条DML语句同时成功和同时失败的呢?
- 在事务的执行过程中,每一条DML的操作都会记录到 InnoDB 存储引擎提供的“事务性活动的日志文件”中。
在事务的执行过程中,我们可以提交事务,也可以回滚事务(回滚永远都是只能回滚到上一次的提交点)
- 提交事务: 清空事务性活动的日志文件,将数据全部彻底持久化到数据库表中。提交事务标志着,事务的结束。并且是一种全部成功的结束。
- 回滚事务: 将之前所有的DML操作全部撤销,并且清空事务性活动的日志文件 , 回滚事务标志着,事务的结束。并且是一种全部失败的结束。
事务具有四个特征 ACID
Atomicity(原子性): 说明事务是最小的工作单元。整个事务中的所有操作,必须作为一个单元全部完成或全部取消。
Consistency(一致性): 在同一个事务当中,所有操作必须同时成功,或者同时失败,以保证数据库从一个一致性状态转换到另一个一致性状态。
Isolation(隔离性): A事务和B事务之间具有一定的隔离。决定A事务在操作一张表的时候,另一个事务B也操作这张表会受到什么影响
Durability(持久性): 事务一旦提交后,该事务对数据库所作的更改将持久地保存在数据库之中,并不会被回滚 , 即使数据库发送故障也不应该对其有任何影响
事务中存在一些概念:
a) 事务(Transaction):一批操作(一组 DML)
b) 开启事务(Start Transaction)
c) 回滚事务(rollback)
d) 提交事务(commit)
e) SET AUTOCOMMIT:禁用或启用事务的自动提交模式
当执行 DML 语句是其实就是开启一个事务
关于事务的回滚需要注意:只能回滚 insert、delete 和 update 语句,不能回滚 select(回滚 select 没有任何意义),对于
create、drop、alter 这些无法回滚.
事务只对 DML 有效果。
注意:rollback,或者 commit 后事务就结束了。
事务的提交与回滚演示
mysql> use bjpowernode;
Database changed
mysql> select * from dept_bak;
Empty set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into dept_bak values(10,'abc', 'tj');
Query OK, 1 row affected (0.00 sec)
mysql> insert into dept_bak values(10,'abc', 'tj');
Query OK, 1 row affected (0.00 sec)
mysql> select * from dept_bak;
+--------+-------+------+
| DEPTNO | DNAME | LOC |
+--------+-------+------+
| 10 | abc | tj |
| 10 | abc | tj |
+--------+-------+------+
2 rows in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from dept_bak;
Empty set (0.00 sec)
mysql> use bjpowernode;
Database changed
mysql> select * from dept_bak;
+--------+-------+------+
| DEPTNO | DNAME | LOC |
+--------+-------+------+
| 10 | abc | bj |
+--------+-------+------+
1 row in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into dept_bak values(20,'abc','bj');
Query OK, 1 row affected (0.00 sec)
mysql> insert into dept_bak values(20,'abc','tj');
Query OK, 1 row affected (0.00 sec)
mysql> insert into dept_bak values(20,'abc','tj');
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from dept_bak;
+--------+-------+------+
| DEPTNO | DNAME | LOC |
+--------+-------+------+
| 10 | abc | bj |
| 20 | abc | tj |
| 20 | abc | tj |
| 20 | abc | tj |
+--------+-------+------+
4 rows in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from dept_bak;
+--------+-------+------+
| DEPTNO | DNAME | LOC |
+--------+-------+------+
| 10 | abc | bj |
| 20 | abc | tj |
| 20 | abc | tj |
| 20 | abc | tj |
+--------+-------+------+
4 rows in set (0.00 sec)
数据库中读取数据存在的三大读问题
- 脏读:在一个事务中读取到其它事务未提交到数据库的数据。
- 不可重复读:在同一个事务当中多次执行同一查询语句,读取到其他事务提交到数据库的数据 , 导致当前事务每次查询到的结果集不一样。
- 幻读:在同一个事务当中多次执行同一查询语句,只要当前事务不结束, 永远读取的都是刚开启该事务时的数据 。脑子中想的数据和真实数据不同
InnoDB 实现了四个隔离级别,用以控制事务所做的修改,并将修改通告至其它并发的事务
隔离级别 | 脏读 | 不可重复读 | **幻读 , **只要是多个事务并发 , 一定是存在幻读的 | 加锁读 |
---|---|---|---|---|
读未提交(READ_UNCOMMITTED) **允许一个事务可以看到其他事务未提交的修改,**这种隔离级别一般都是理论上的,大多数的数据库隔离级别都是二档起步 | 有 | 有 | 有 | 不加锁 |
读已提交(READ_COMMITTED) **允许一个事务只能看到其他事务已经提交的数据,未提交的修改是不可见的。**这种隔离级别是比较真实的数据,每一次读到的数据是绝对的真实 (Oracle默认级别) | 无 | 有 | 有 | 不加锁 |
可重复读(REPEATABLE_READ) **确保如果在一个事务中执行两次相同的 SELECT 语句,都能得到相同的结果,不管其他事务是否提交这些修改。**永远读取的都是刚开启事务时的数据(MySql默认级别) | 无 | 无 | 有 | 不加锁 |
序列化(SERIALIZABLE)**将一个事务与其他事务完全地隔离。这是最高隔离级别,**每一次读取到的数据都是最真实的,解决了所有的问题 , 但是事务排队执行不能并发 , 效率最低。 | 无 | 无 | 无 | 加锁 |
设置服务器缺省隔离级别
A教室和B教室中间有一道墙,这道墙可以很厚,也可以很薄。这就是事务的隔离级别。这道墙越厚,表示隔离级别就越高。
通过修改配置文件设置
可以在 my.ini 文件中使用 transaction-isolation 选项来设置服务器的缺省事务隔离级别。
#该选项值可以是:READ-UNCOMMITTED , READ-COMMITTED , REPEATABLE-READ , SERIALIZABLE
[mysqld]
#设置隔离级别 , 如果没有设置 , 默认是REPEATABLE-READ
transaction-isolation = READ-COMMITTED
通过命令在运行的服务器中动态设置隔离级别
- 设置完隔离级别后需要退出MYSQL服务重新进入
--其中的<isolation-level>可以是:READ UNCOMMITTED , READ COMMITTED , REPEATABLE READ , SERIALIZABLE
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL <isolation-level>
事务隔离级别的作用范围分为两种
- 全局级(GLOBAL):对所有的会话有效
- 会话级(SESSION):只对当前的会话有效 , 默认设置的是当前
--设置会话级隔离级别为 READ COMMITTED
mysql> SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
--设置会话级隔离级别为 READ COMMITTED
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
--设置全局级隔离级别为 READ COMMITTED :
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
查看隔离级别
为了查看当前隔离级别,可访问服务器变量 tx_isolation(默认保存保存着当前的会话隔离级别)
--查看会话级的当前隔离级别
mysql> SELECT @@tx_isolation;
--查看会话级的当前隔离级别:
mysql> SELECT @@session.tx_isolation;
--查看全局级的当前隔离级别:
mysql> SELECT @@global.tx_isolation;
验证四种隔离界别
验证:read uncommited
事务A | 事务B |
---|---|
s1>use bjpowernode; | s2>set global transaction isolation level read uncommitted; |
s1>create table tx ( id int(11),num int (10)); | s2>use bjpowernode; |
s1>start transaction; | s2>start transaction; |
s2>select * from tx;(空表) | |
s1>insert into tx values (1,10); | |
s2>select * from tx;(读取到数据) | |
s1>rollback; | |
s2>select * from tx; |
验证:read commited
事务A | 事务B |
---|---|
s1>use bjpowernode | s2> set global transaction isolation level read committed; |
s1>start transaction; | s2>use bjpowernode; |
s2>start transaction; | |
s2>select * from tx;(空表) | |
s1>insert into tx values (1,10); | |
s2>select * from tx;(空表) | |
s1>commit; | |
s2>select * from tx;(读取到数据) |
验证:repeatable read
事务A | 事务B |
---|---|
s1>use bjpowernode | s2>set global transaction isolation level repeatable read; |
s1>start transaction; | s2>use bjpowernode; |
s2>start transaction; | |
s2>select * from tx;(原先的数据) | |
s1>insert into tx values (1,10); | |
s1>commit; | |
s2>select * from tx;(原先的数据) |
验证:serializable
事务A | 事务B |
---|---|
s1>use bjpowernode | s2>set global transaction isolation level serializable; |
s1>start transaction; | s2>use bjpowernode; |
select * from tx; | s2>start transaction; |
s1>insert into tx values (1,10); | |
s2>select * from tx;(不能访问tx表) |