剑指offer:一年又11天 |
---|
事务就是一组DML语句,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,看做一个整体。MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。
案例:早八起床
现在有一个穿衣服的事务,这个事务有多条穿衣服指令:穿毛衣、穿外套、穿裤子。
开始起床…,穿好了毛衣穿外套,穿好了外套穿… 突然发现裤子找不着了,那么现在该怎么办?
现在有几个选择:1. 找一条新的裤子 2. 不穿裤子直接去上课 3. 脱了衣服继续睡 等等
而在MySQL看来,由于穿衣服这个事务并没有全部执行成功,因此它会自动回到穿衣服之前的状态 - - 也就是把之前穿上的衣服全部脱掉。
所有,一个完整的事务,绝对不是简单的 sql 集合,还需要满足如下四个属性:
上面四个属性,可以简称为 ACID 。
原子性(Atomicity,或称不可分割性)
一致性(Consistency)
隔离性(Isolation,又称独立性)
持久性(Durability)。
事务被 MySQL 编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题.可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?因此事务本质上是为了应用层服务的.而不是伴随着数据库系统天生就有的.
(既: 事务并不是MySQL一开始就有的,而是为了方便使用所以后来增加的。)
备注:我们后面把 MySQL 中的一行信息,称为一行记录
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持。
查看数据库引擎
mysql> show engines\G
*************************** 1. row ***************************
Engine: InnoDB -- 引擎名称
Support: DEFAULT -- 默认引擎
Comment: Supports transactions, row-level locking, and foreign keys
Transactions: YES -- 支持事务:是
XA: YES
Savepoints: YES -- 支持事务保存点:是
*************************** 2. row ***************************
Engine: MRG_MYISAM
Support: YES
Comment: Collection of identical MyISAM tables
Transactions: NO
XA: NO
Savepoints: NO
...
...
查看事务提交方式
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
用 SET 来改变 MySQL 的自动提交模式:
mysql> set autocommit = 0; #SET AUTOCOMMIT=0 禁止自动提交
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
mysql> set autocommit = 1;#SET AUTOCOMMIT=0 开启自动提交
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
-- 为了便于演示,我们将mysql的默认隔离级别设置成读未提交。
-- 具体操作后面会讲,现在以使用为主。
mysql> select @@global.tx_isolation; -- 查看全局隔离等级
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ | -- 可重复读(这是MySQL默认隔离等级)
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@tx_isolation; -- 查看本次会话隔离等级(启动会话时默认与全局一致)
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ | -- 可重复读
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> set global transaction isolation level read uncommitted; -- 修改隔离等级为:读未提交
Query OK, 0 rows affected (0.00 sec)
mysql> select @@global.tx_isolation; -- 全局隔离等级
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED | -- 已修改为:读未提交
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ | -- 仍然是:可重复读
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> quit -- 退出
-- 重新连接MySQL
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED | -- 已修改为:读未提交
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED | -- 已修改为:读未提交
+------------------+
1 row in set, 1 warning (0.00 sec)
简单银行用户表
mysql> create table if not exists account(
-> id int primary key,
-> name varchar(50) not null default '',
-> blance decimal(10,2) not null default 0.0
-> )ENGINE=InnoDB DEFAULT CHARSET=UTF8;
Query OK, 0 rows affected (0.03 sec)
用户A 和 用户B 同时访问数据库,这里是结果图,执行流程看下方代码
-- 用户A
mysql> start transaction;-- 启动一个事务的两种方法之一
Query OK, 0 rows affected (0.00 sec)
mysql> savepoint p1; -- 打一个保存点p1
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(1, '张三', 4212.1); -- 插入用户张三
Query OK, 1 row affected (0.00 sec)
mysql> savepoint p2; -- 打一个保存点p2
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(2, '李四', 1242.1); -- 插入用户李四
Query OK, 1 row affected (0.00 sec)
mysql> savepoint p3; -- 打一个保存点p3
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(3, '王五', 2344.5); -- 插入用户王五
Query OK, 1 row affected (0.00 sec)
-- 回滚到保存点p3 -- p3之后做的事情全部撤销,这里的表现应该是王五的信息没有了
mysql> rollback to p3;
Query OK, 0 rows affected (0.00 sec)
-- 回滚到保存点p1 -- 这里的表现就是张三和李四的数据也都没有了
mysql> rollback to p1;
Query OK, 0 rows affected (0.01 sec)
-- A提交事务(代表事务完成),因为我们全部都回滚了,因此这次事务虽然完成了,但是也相当于什么都没做
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
-- 用户B
-- 启动一个事务的两种方法之二:推荐使用begin,因为简单
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> desc account;
+--------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(50) | NO | | | |
| blance | decimal(10,2) | NO | | 0.00 | |
+--------+---------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
-- A插入张三用户后 B进行查看:可以查到A插入的数据(注意,此时A还未提交)
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
+----+--------+---------+
1 row in set (0.00 sec)
-- A插入李四和王五的信息后 B进行查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 2344.50 |
+----+--------+---------+
3 rows in set (0.00 sec)
-- A回滚到p3后,B进行查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
+----+--------+---------+
2 rows in set (0.00 sec)
-- A回滚到p1后,B进行查看
mysql> select * from account;
Empty set (0.00 sec)
-- A提交事务后,B进行查看
mysql> select * from account;
Empty set (0.00 sec)
-- B提交事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
补充:不打保存点也可以回滚:rollback;
,直接回滚到事务最开始。
-- 用户A启动事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(1, '张三', 4212.1); -- 插入用户张三
Query OK, 1 row affected (0.00 sec)
mysql> insert into account values(2, '李四', 1242.1); -- 插入用户李四
Query OK, 1 row affected (0.00 sec)
-- ctrl + \:终止进程,模拟客户端崩溃
mysql> Aborted
-- 用户B启动事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- A插入用户张三 B进行查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
+----+--------+---------+
1 row in set (0.00 sec)
-- A插入用户李四 B进行查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
+----+--------+---------+
2 rows in set (0.00 sec)
-- A客户端崩溃 B进行查看 (account为空,自动回滚到A插入之前的状态)
mysql> select * from account;
Empty set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
-- 用户A启动事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(1, '张三', 4212.1);
Query OK, 1 row affected (0.00 sec)
mysql> insert into account values(2, '李四', 1242.1);
Query OK, 1 row affected (0.00 sec)
-- 提交事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
-- 客户端崩溃
mysql> Aborted
-- 用户B启动事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- A插入用户张三后 B进行查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
+----+--------+---------+
1 row in set (0.00 sec)
-- A插入用户李四后 B进行查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
+----+--------+---------+
2 rows in set (0.00 sec)
-- A提交事务后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
+----+--------+---------+
2 rows in set (0.00 sec)
-- A客户端崩溃 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
+----+--------+---------+
2 rows in set (0.00 sec)
-- B提交事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
-- B退出客户端
mysql> quit
Bye
-- B重新连接客户端后 查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
+----+--------+---------+
2 rows in set (0.00 sec)
-- 前面几个测试中,我们的 事务自动提交 一直是开启的,但是为什么如果没手动提交还是会自动回滚
mysql> show variables like "autocommit";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
-- 把 事务自动提交 关闭再测试一下
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like "autocommit";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
-- 用户A启动事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(3, '王五', 5434.5);
Query OK, 1 row affected (0.00 sec)
mysql> insert into account values(4, '赵六', 1231.2);
Query OK, 1 row affected (0.00 sec)
-- 用户A退出mysql
mysql> quit;
Bye
-- 用户B启动事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- A插入用户王五后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
+----+--------+---------+
3 rows in set (0.00 sec)
-- A插入用户赵六后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
| 4 | 赵六 | 1231.20 |
+----+--------+---------+
4 rows in set (0.00 sec)
-- A退出mysql后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
+----+--------+---------+
2 rows in set (0.00 sec)
我们发现:不管自动提交是否开启,如果事务未提交就退出,事务都会回滚而不是自动提交。
结论?
实验一:
-- 用户A
-- 自动提交开启
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
mysql> insert into account values(3, '王五', 5434.5);
Query OK, 1 row affected (0.00 sec)
mysql> insert into account values(4, '赵六', 1231.2);
Query OK, 1 row affected (0.01 sec)
mysql> quit
Bye
-- 用户B
-- A插入客户王五后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
+----+--------+---------+
3 rows in set (0.00 sec)
-- A插入客户赵六后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
| 4 | 赵六 | 1231.20 |
+----+--------+---------+
4 rows in set (0.00 sec)
-- A退出后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
| 4 | 赵六 | 1231.20 |
+----+--------+---------+ -- 数据保存了
4 rows in set (0.00 sec)
实验二:
-- 用户A
-- 关闭自动提交
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
mysql> insert into account values(5, '孙七', 1234.5);
Query OK, 1 row affected (0.00 sec)
mysql> insert into account values(6, '周八', 1234.5);
Query OK, 1 row affected (0.00 sec)
mysql> quit;
Bye
-- 用户B
-- A插入客户孙七后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
| 4 | 赵六 | 1231.20 |
| 5 | 孙七 | 1234.50 |
+----+--------+---------+
5 rows in set (0.00 sec)
-- A插入客户周八后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
| 4 | 赵六 | 1231.20 |
| 5 | 孙七 | 1234.50 |
| 6 | 周八 | 1234.50 |
+----+--------+---------+
6 rows in set (0.00 sec)
-- A退出后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
| 4 | 赵六 | 1231.20 |
+----+--------+---------+ -- 数据未保存
4 rows in set (0.00 sec)
实验三:
-- 用户A
-- 仍然关闭自动提交
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(5, '孙七', 1234.5);
Query OK, 1 row affected (0.00 sec)
-- A手动提交事务
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> quit;
Bye
-- 用户B
-- A插入客户孙七后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
| 4 | 赵六 | 1231.20 |
| 5 | 孙七 | 1234.50 |
+----+--------+---------+
5 rows in set (0.00 sec)
-- A提交事务并退出后 B查看
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 4212.10 |
| 2 | 李四 | 1242.10 |
| 3 | 王五 | 5434.50 |
| 4 | 赵六 | 1231.20 |
| 5 | 孙七 | 1234.50 | -- 数据保存成功了
+----+--------+---------+
5 rows in set (0.00 sec)
大家是否理解了单条SQL的意义,以及自动提交的含义?
单条SQL本质上也是一个事务,而自动提交是针对与这样没有在begin内的事务的。
上面我们说事务的提交方式有两种:自动提交和手动提交 当我们使用begin/start
transolation时,提交方式就会变成手动提交,自动提交设置就不起作用了;
当只是单条SQL时,MySQL会将单条SQL封装成一个事务,并且根据自动提交的设置来决定是否提交该事务。
结论:
从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit)
那么隔离性?一致性?
事务操作注意事项:
MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。
但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
就如同你妈妈给你说:你要么别学,要学就学到最好。至于你怎么学,中间有什么困难,你妈妈不关心。那么你的学习,对你妈妈来讲,就是原子的。那么你学习过程中,很容易受别人干扰,此时,就需要将你的学习隔离开,保证你的学习环境是健康的。
数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别
隔离级别
简单理解:
读未提交:其他事务没有提交的执行结果都可以看到;
读提交:可以看到其他事务提交了的执行结果,但是有一个问题:你的这个事务连续读了5次,在你读的期间,其他事务连续提交了好多次,那么你读到的5次结果就不一样,所以会遇到不可重复读的情况;
可重复读:可以看到其他事务提交了的执行结果,而且,在一次事务中,从第一次读开始到这次事务结束,每次读到的数据都是一样的,不受其他事务提交的影响。
串行化:写操作排队,我的事务执行结束之前,不允许其他事务进行写操作。
隔离级别如何实现:隔离,基本都是通过锁实现的,不同的隔离级别,锁的使用是不同的。常见有,表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key锁(GAP+行锁)等。不过,我们目前现有这个认识就行,先关注上层使用。
查看:
mysql> select @@global.tx_isolation;-- 查看全局隔离级别
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@session.tx_isolation;-- 查看当前会话隔离级别
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@tx_isolation;-- 同上
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
设置:
-- 设置当前会话 or 全局隔离级别语法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE};
-- 设置全局隔离级别为 可重复读(RR)
mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@global.tx_isolation; -- 查看全局
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@session.tx_isolation; -- 查看当前会话 ,没有改变
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set, 1 warning (0.00 sec)
-- 设置会话隔离级别为串行化
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@session.tx_isolation; -- 查看当前会话
+------------------------+
| @@session.tx_isolation |
+------------------------+
| SERIALIZABLE |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@tx_isolation; -- 查看当前会话
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
--几乎没有加锁,虽然效率高,但是问题太多,严重不建议采用
--终端A
-- 设置隔离级别为 读未提交
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
--重启客户端
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql> update account set blance=123.0 where id=1; --更新指定行
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--没有commit哦!!!
--终端B
mysql> begin;
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 123.00 | --读到终端A更新但是未commit的数据[insert,delete同样]
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
--一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读
(dirty read)
-- 终端A
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
--重启客户端
mysql> select * from account; --查看当前数据
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 123.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; --手动开启事务,同步的开始终端B事务
Query OK, 0 rows affected (0.00 sec)
mysql> update account set blance=321.0 where id=1; --更新张三数据
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--切换终端到终端B,查看数据。
mysql> commit; --commit提交!
Query OK, 0 rows affected (0.01 sec)
--切换终端到终端B,再次查看数据。
--终端B
mysql> begin; --手动开启事务,和终端A一前一后
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --终端A commit之前,查看不到
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 123.00 | --老的值
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
--终端A commit之后,看到了!
--but,此时还在当前事务中,并未commit,那么就造成了,同一个事务内,同样的读取,在不同的时间段
--(依旧还在事务操作中!),读取到了不同的值,这种现象叫做不可重复读(non reapeatable read)!!
--(这个是问题吗??)
mysql> select *from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 | --新的值
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
--终端A
mysql> set global transaction isolation level repeatable read; --设置全局隔离级别
RR
Query OK, 0 rows affected (0.01 sec)
--关闭终端重启
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ | --隔离级别RR
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> select *from account; --查看当前数据
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; --开启事务,同步的,终端B也开始事务
Query OK, 0 rows affected (0.00 sec)
mysql> update account set blance=4321.0 where id=1; --更新数据
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--切换到终端B,查看另一个事务是否能看到
mysql> commit; --提交事务
--切换终端到终端B,查看数据。
--终端B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --终端A中事务 commit之前,查看当前表中数据,数据未更新
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; --终端A中事务 commit 之后,查看当前表中数据,数据未更新
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
--可以看到,在终端B中,事务无论什么时候进行查找,看到的结果都是一致的,这叫做可重复读!
mysql> commit; --结束事务
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --再次查看,看到最新的更新数据
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
----------------------------------------------------------------
--如果将上面的终端A中的update操作,改成insert操作,会有什么问题??
--终端A
mysql> select *from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; --开启事务,终端B同步开启
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account (id,name,blance) values(3, '王五', 5432.0);
Query OK, 1 row affected (0.00 sec)
--切换到终端B,查看另一个事务是否能看到
mysql> commit; --提交事务
Query OK, 0 rows affected (0.00 sec)
--切换终端到终端B,查看数据。
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
--终端B
mysql> begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --终端A commit前 查看
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; --终端A commit后 查看
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; --多次查看,发现终端A在对应事务中insert的数据,在终端B的事
-- 务周期中,也没有什么影响,也符合可重复的特点。但是,一般的数据库在可重复读情况的时候,无法屏蔽其
-- 他事务insert的数据(为什么?因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存
-- 在,那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读
-- 情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读
-- (phantom read)。很明显,MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-Key锁
-- (GAP+行锁)解决的。这块比较难,有兴趣可以去了解一下)。
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> commit; --结束事务
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --看到更新
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
--对所有操作全部加锁,进行串行化,不会有问题,但是只要串行化,效率很低,几乎完全不会被采用
--终端A
mysql> set global transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin; --开启事务,终端B同步开启
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --两个读取不会串行化,共享锁
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
mysql> update account set blance=1.00 where id=1; --终端A中有更新或者其他操作,会阻塞。直到终端B事务提交。
Query OK, 1 row affected (18.19 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--终端B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --两个读取不会串行化
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
mysql> commit; --提交之后,终端A中的update才会提交。
Query OK, 0 rows affected (0.00 sec)
总结:
一致性(Consistency)