博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
mysql 事务
阅读量:6813 次
发布时间:2019-06-26

本文共 13074 字,大约阅读时间需要 43 分钟。

hot3.png

-------------------------------------------------------------------------------------------------------------------------------------------

参考文档:

-------------------------------------------------------------------------------------------------------------------------------------------

mysql:

C:\WINDOWS\system32>mysql -Vmysql  Ver 14.14 Distrib 5.5.27, for Win64 (x86)

-------------------------------------------------------------------------------------------------------------------------------------------

1.事务的四大特性(ACID):

原子性(Atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。

一致性(Consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态。

隔离性(Isolation):通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。

持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

2.事务的并发问题:

3种数据读取问题(脏读,不可重复读,幻读)、2种数据更新问题

脏读:A事务读取B事务尚未commit的数据,并在此基础上操作,之后B事务进行rollback,那么A事务读取到的数据就是脏数据.

时间 转账事务A 取款事务B
1 开启事务  
2   开启事务
3   查询账户余额为1000元
4   取出500元余额修改为500元
5 查询账户余额为500元(脏读)  
6   撤销事务余额恢复为1000元
7 汇入100元把余额修改为600元  
8 提交事务  

不可重复读:事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了。

时间 转账事务A 取款事务B
1   开始事务
2 开始事务  
3   查询账户余额为1000元
4 查询账户余额为1000元  
5   取出100元修改余额为900元
6   提交事务
7 查询账户余额为900元(不可重复读)  

幻读:事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行。

时间 转账事务A 取款事务B
1   开始事务
2 开始事务  
3 统计总存款为10000元  
4   新增一个存款账户存入100元
5   提交事务
6 再次统计总存款为10100元(幻读)  

第1类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了。

时间 取款事务A 转账事务B
T1 开始事务  
T2   开始事务
T3 查询账户余额为1000元  
T4   查询账户余额为1000元
T5   汇入100元修改余额为1100元
T6   提交事务
T7 取出100元将余额修改为900元  
T8 撤销事务  
T9 余额恢复为1000元(丢失更新)  

第2类丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失。

时间 转账事务A 取款事务B
T1   开始事务
T2 开始事务  
T3   查询账户余额为1000元
T4 查询账户余额为1000元  
T5   取出100元将余额修改为900元
T6   提交事务
T7 汇入100元将余额修改为1100元  
T8 提交事务  
T9 查询账户余额为1100元(丢失更新)  

3.事务隔离级别:

数据并发访问所产生的问题,数据库通常会通过锁机制来解决。

按锁定对象不同可以分为表级锁和行级锁;

按并发事务锁定关系可以分为共享锁和独占锁;

直接使用锁是非常麻烦的,为此数据库为用户提供了自动锁机制,只要用户指定会话的事务隔离级别,数据库就会通过分析SQL语句然后为事务访问的资源加上合适的锁,此外,数据库还会维护这些锁通过各种手段提高系统的性能,这些对用户来说都是透明的。

隔离级别 脏读 不可重复读 幻读 第一类丢失更新 第二类丢失更新
READ UNCOMMITED 允许 允许 允许 不允许 允许
READ COMMITTED 不允许 允许 允许 不允许 允许
REPEATABLE READ 不允许 不允许 允许 不允许 不允许
SERIALIZABLE 不允许 不允许 不允许 不允许 不允许

事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。

mysql 默认的事务隔离级别是:可重复读(REPEATABLE READ)

mysql> select @@tx_isolation;+-----------------+| @@tx_isolation  |+-----------------+| REPEATABLE-READ |+-----------------+1 row in set (0.00 sec)

读未提交:

1.打开终端A,设置当前会话的事务隔离级别为read uncommitted。然后开启事务T1,查询表test中id为1的记录

mysql> set session transaction isolation level read uncommitted;Query OK, 0 rows affected (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=1;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  1 | read uncommitted          |  100 | 读未提交   |+----+---------------------------+------+----------+1 row in set (0.00 sec)

2.打开终端B,设置当前会话的事务隔离级别为read uncommitted。然后开启事务T2,更新表test中id为1的记录中的data字段

mysql> set session transaction isolation level read uncommitted;Query OK, 0 rows affected (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=1;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  1 | read uncommitted          |  100 | 读未提交       |+----+---------------------------+------+----------+1 row in set (0.00 sec)mysql> update test set data=data-1 where id=1;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> select * from test where id=1;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  1 | read uncommitted          |   99 | 读未提交       |+----+---------------------------+------+----------+1 row in set (0.00 sec)

此时终端B开启的事务T2,更新数据之后并未提交。

3.切换到终端A,再次查询表test中id为1的记录

mysql> select * from test where id=1;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  1 | read uncommitted          |   99 | 读未提交       |+----+---------------------------+------+----------+1 row in set (0.00 sec)

可以发现,虽然终端B上开启的事务T2尚未提交,但是终端A上开启的事务T1仍然可以查询到事务T2已经更新的数据。此时,如果事务T2进行回滚操作,那么事务T1查询到的数据就是脏数据。

4.切换到终端B,进行回滚操作

mysql> select * from test where id=1;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  1 | read uncommitted          |  100 | 读未提交       |+----+---------------------------+------+----------+1 row in set (0.00 sec)

5.切换到终端A,更新表test中id为1的记录的data字段,更新之前查到的记录中data字段的值是99

mysql> update test set data=data-1 where id=1;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> select * from test where id=1;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  1 | read uncommitted          |   99 | 读未提交       |+----+---------------------------+------+----------+1 row in set (0.00 sec)

可以发现data字段的值仍是99=100-1,而并非是98=99-1

读已提交:

1.切换到终端A,设置事务隔离级别为read committed。然后开启事务T1,查询表test中id为2的记录

mysql> set session transaction isolation level read committed;Query OK, 0 rows affected (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=2;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  2 | read committed            |  100 | 读已提交        |+----+---------------------------+------+----------+1 row in set (0.00 sec)

2.打开终端B,设置当前会话的事务隔离级别为read committed。然后开启事务T2,更新表test中id为2的记录中的data字段

mysql> set session transaction isolation level read committed;Query OK, 0 rows affected (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=2;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  2 | read committed            |  100 | 读已提交        |+----+---------------------------+------+----------+1 row in set (0.00 sec)mysql> update test set data=data-1 where id=2;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> select * from test where id=2;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  2 | read committed            |   99 | 读已提交        |+----+---------------------------+------+----------+1 row in set (0.00 sec)

此时终端B开启的事务T2,更新数据之后并未提交。

3.切换到终端A,查询表test中id为2的记录

mysql> select * from test where id=2;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  2 | read committed            |  100 | 读已提交        |+----+---------------------------+------+----------+1 row in set (0.00 sec)

可以发现只要事务T2还没有提交,那么事务T1中就查询不到T2更新的数据,解决了脏读的问题。

4.切换到终端B,进行提交操作

mysql> commit;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=2;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  2 | read committed            |   99 | 读已提交        |+----+---------------------------+------+----------+1 row in set (0.00 sec)

5.切换到终端A,查询表test中id为2的记录

mysql> select * from test where id=2;+----+---------------------------+------+----------+| id | transactionIsolationLevel | data | desc     |+----+---------------------------+------+----------+|  2 | read committed            |   99 | 读已提交        |+----+---------------------------+------+----------+1 row in set (0.00 sec)

此时,事务T1 就能查询到事务T2更新的数据。事务T2提交前后,事务T1各查询了一次,第一次查询到的data值为100,第二次查询到的data值是99,这就是所谓的不可重复读。

可重复读:

1.打开终端A,设置事务隔离级别为repeatable read。然后开启事务T1,查询表test中id为3的记录

mysql> set session transaction isolation level repeatable read;Query OK, 0 rows affected (0.00 sec)mysql> select @@tx_isolation;+-----------------+| @@tx_isolation  |+-----------------+| REPEATABLE-READ |+-----------------+1 row in set (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=3;+----+---------------------------+------+--------+| id | transactionIsolationLevel | data | desc   |+----+---------------------------+------+--------+|  3 | repeatable read           |  100 | 重复读      |+----+---------------------------+------+--------+1 row in set (0.00 sec)

2.打开终端B,设置当前会话的事务隔离级别为repeatable read。然后开启事务T2,更新表test中id为3的记录中的data字段并提交

mysql> set session transaction isolation level repeatable read;Query OK, 0 rows affected (0.00 sec)mysql> select @@tx_isolation;+-----------------+| @@tx_isolation  |+-----------------+| REPEATABLE-READ |+-----------------+1 row in set (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=3;+----+---------------------------+------+--------+| id | transactionIsolationLevel | data | desc   |+----+---------------------------+------+--------+|  3 | repeatable read           |  100 | 重复读      |+----+---------------------------+------+--------+1 row in set (0.00 sec)mysql> update test set data=data-1 where id=3;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> commit;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=3;+----+---------------------------+------+--------+| id | transactionIsolationLevel | data | desc   |+----+---------------------------+------+--------+|  3 | repeatable read           |   99 | 重复读      |+----+---------------------------+------+--------+1 row in set (0.00 sec)

3.切换到终端A,查询表test中id为3的记录

mysql> select * from test where id=3;+----+---------------------------+------+--------+| id | transactionIsolationLevel | data | desc   |+----+---------------------------+------+--------+|  3 | repeatable read           |  100 | 重复读      |+----+---------------------------+------+--------+1 row in set (0.00 sec)

终端A下的两次查询结果一致,没有出现不可重复读的问题。

可重复读的隔离级别下使用了MVCC机制,T1事务中读取的是记录的快照版本,而非最新版本,T2事务的更新是创建了一个新版本来更新,不同事务的读和写是分离的。

此时,若在终端A上对data进行更新操作,比如data=data-1;得到的结果是98=99-1,而不是99=100-1 ----数据的一致性

mysql> update test set data=data-1 where id=3;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> commit;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=3;+----+---------------------------+------+--------+| id | transactionIsolationLevel | data | desc   |+----+---------------------------+------+--------+|  3 | repeatable read           |   98 | 重复读      |+----+---------------------------+------+--------+1 row in set (0.00 sec)

串行化:

1.打开终端A,设置事务隔离级别为serializable。然后开启事务T1,查询表test中id为4的记录

mysql> set session transaction isolation level serializable;Query OK, 0 rows affected (0.00 sec)mysql> select @@tx_isolation;+----------------+| @@tx_isolation |+----------------+| SERIALIZABLE   |+----------------+1 row in set (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=4;+----+---------------------------+------+--------+| id | transactionIsolationLevel | data | desc   |+----+---------------------------+------+--------+|  4 | serializable              |  100 | 串行化      |+----+---------------------------+------+--------+1 row in set (0.00 sec)

2.打开终端B,设置当前会话的事务隔离级别为serializable。然后开启事务T2

mysql> set session transaction isolation level serializable;Query OK, 0 rows affected (0.00 sec)mysql> select @@tx_isolation;+----------------+| @@tx_isolation |+----------------+| SERIALIZABLE   |+----------------+1 row in set (0.00 sec)mysql> start transaction;Query OK, 0 rows affected (0.00 sec)

查询表test中id为4的记录

mysql> select * from test where id=4;+----+---------------------------+------+--------+| id | transactionIsolationLevel | data | desc   |+----+---------------------------+------+--------+|  4 | serializable              |  100 | 串行化      |+----+---------------------------+------+--------+1 row in set (0.00 sec)

更新表test中id为4的记录

mysql> update test set data=data-1 where id=4;ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

更新失败。此时表test已经被事务T1加上了读锁

3.切换到终端A,执行提交或是回滚的操作,结束了事务T1。

mysql> commit;Query OK, 0 rows affected (0.00 sec)

4.切换到终端B,再此执行update语句

mysql> update test set data=data-1 where id=4;Query OK, 1 row affected (0.00 sec)Rows matched: 1  Changed: 1  Warnings: 0mysql> commit;Query OK, 0 rows affected (0.00 sec)mysql> select * from test where id=4;+----+---------------------------+------+--------+| id | transactionIsolationLevel | data | desc   |+----+---------------------------+------+--------+|  4 | serializable              |   99 | 串行化      |+----+---------------------------+------+--------+1 row in set (0.00 sec)

sql语句就可以正常执行了。

以下是我在serializable事务隔离级别下做的测试,√表示的是执行过程中一切正常,×则表示在执行最后一步select/update语句时报错。
Lock wait timeout exceeded; try restarting transaction

以上只测试了两个事务并发执行的情况,更多事务并发,后续再测。

转载于:https://my.oschina.net/u/3229807/blog/1926021

你可能感兴趣的文章
【转】linux下tty,控制台,虚拟终端,串口,console(控制台终端)详解----不错...
查看>>
Vertica增加一个数据存储的目录
查看>>
小小的告别一下这个博客
查看>>
【转】内核编译时, 到底用make clean, make mrproper还是make distclean(转载)
查看>>
The YubiKey NEO
查看>>
看一下你在中国属于哪个阶层?
查看>>
Collections.sort方法对list排序的两种方式
查看>>
Synchronize Ultimate
查看>>
设计模式之模板方法模式
查看>>
关于配置
查看>>
如何更好的通过Inflate layout的方式来实现自定义view
查看>>
js 加载初始化日期
查看>>
smali语法中文版
查看>>
快如闪电、超轻量级的基于.Net平台的依赖注入框架Ninject
查看>>
Effective C++ -- 继承和面向对象设计
查看>>
在信息技术和加密算法
查看>>
springmvc+mongodb+maven 项目测试代码
查看>>
Junit指定测试运行顺序
查看>>
android变化HOLO对话风格
查看>>
windows 10 install oracle 12c error:[ INS-30131 ]
查看>>