本文中的实验测试环境: Windows 10 + cmd + MySQL5.6.36 + InnoDB
首先,交易的基本要素(ACID)
1. 原子性: 事务开始后
2. 一致性(Consistency): 在事务之前和之后,的完整性约束没有被破坏. 例如,如果A汇款到B,则A不可能扣款,但B尚未收到.
3. 隔离: 同时隔离级别 详解,只允许一个事务请求相同的数据,并且不同的事务不会相互干扰. 例如,A正在从银行卡提款. 在A的提款过程结束之前,B无法将钱转入该卡.
4. 耐用性: 交易完成后,交易对所做的所有更新将保存到中,并且无法回滚.
摘要: 原子性是事务隔离的基础,隔离和持久性是手段,最终目的是保持数据一致性.
第二,交易并发问题
1. 脏读: 事务A读取由事务B更新的数据,然后B回滚操作,然后A读取的数据是脏数据
2. 不可重复读取: 事务A多次读取同一数据,而事务B在多次读取过程中更新并提交数据,导致事务A多次读取同一数据. 结果不一致.
3. 妙读: 系统管理员A将中所有学生的成绩从特定分数更改为ABCDE分数,但是系统管理员B此时插入了特定分数的记录. 当系统管理员A更改后结束后,我发现还有另一条记录未更改,就好像发生了幻觉一样. 这称为幻影阅读.
摘要: 不可重复读取和幻像读取很容易混淆. 不可重复读注重修改,而幻像读注重添加或删除. 要解决不可重复读取的问题,您只需要锁定满足条件的行,而对于不可思议的读取,则需要锁定表
三,MySQL事务隔离级别
mysql的默认事务隔离级别是repeatable-read
四,使用示例来说明每个隔离级别的情况
1. 未提交阅读:
(1)打开客户端A,并将当前事务模式设置为读取未提交(读取未提交),查询表帐户的初始值:
(2)在提交客户A的交易之前,请打开另一个客户B并更新表格帐户:
(3)目前,尽管客户B的交易尚未提交,但客户A可以查询B的更新数据:
(4)一旦由于某种原因回滚了客户B的交易,所有操作都将被撤消,那么客户A查询的数据实际上就是脏数据:
(5)在客户端A上执行更新语句update account set balance = balance-50,其中id = 1,lilei的余额没有变为350,但实际上为400. 一致性并不奇怪. 不问数据?如果您这样认为,那是天真的. 在该应用程序中,我们将使用400-50 = 350,并且不知道其他会话已回滚. 要解决此问题,您可以使用提交的提交的隔离级别
2. 阅读已提交
(1)打开客户端A,并将当前事务模式设置为读取已提交(未提交读取),即查询表帐户的初始值:
(2)在提交客户A的交易之前,请打开另一个客户B并更新表格帐户:
(3)此时,客户端B的事务尚未提交,客户端A无法查询B的更新数据,从而解决了读取不干净的问题:
(4)客户B的交易提交
(5)客户端A执行与上一步相同的查询,结果与上一步不一致,即存在不可重复读取的问题. 在应用程序中,假设我们在客户端A的会话中并查询lilei的余额为450,但是其他事务将lilei的余额值更改为400. 我们不知道如果您使用450进行其他操作,问题,但概率确实很小. 如果要避免此问题,可以使用可重复的隔离级别
3. 重复阅读
(1)打开一个客户端A,并将当前事务模式设置为可重复读取,查询表帐户的初始值:
(2)在提交客户A的交易之前,请打开另一个客户B,更新表格帐户并提交. 客户B的交易实际上可以修改客户A的交易查询的行,即mysql. 重复读取不会锁定事务查询的行. 这超出了我的预期. 当sql标准中的事务隔离级别是可重复读取时,读取和写入操作必须锁定该行. MySQL实际上没有锁,所以我去了. 注意锁定应用程序中的行,否则您将使用步骤(1)中lilei的余额作为中间值400进行其他操作
(3)在客户端A上执行步骤(1)的查询:
(4)执行步骤(1),lilei的余额仍为400,步骤(1)的查询结果一致,没有不可重复读取的问题;然后执行更新余额= balance-50,其中id = 1,余额不等于400-50 = 350,在步骤(2)中通过350计算lilei的余额值,因此为300,数据的一致性为没有销毁,这有点神奇,也许这是mysql <
的特征
mysql> select * from account;
+------+--------+---------+
| id | name | balance |
+------+--------+---------+
| 1 | lilei | 400 |
| 2 | hanmei | 16000 |
| 3 | lucy | 2400 |
+------+--------+---------+
rows in set (0.00 sec)
mysql> update account set balance = balance - 50 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from account;
+------+--------+---------+
| id | name | balance |
+------+--------+---------+
| 1 | lilei | 300 |
| 2 | hanmei | 16000 |
| 3 | lucy | 2400 |
+------+--------+---------+
rows in set (0.00 sec)
(5)在客户端A上开始交易,并查询表帐户的初始值
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+------+--------+---------+
| id | name | balance |
+------+--------+---------+
| 1 | lilei | 300 |
| 2 | hanmei | 16000 |
| 3 | lucy | 2400 |
+------+--------+---------+
rows in set (0.00 sec)
(6)在客户端B上开始交易,添加新数据,余额字段值为600,然后提交
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(4,'lily',600);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
(7)计算客户A的余额之和,值为300 + 16000 + 2400 = 18700,不包括客户B的值,然后计算客户A提交后的余额之和,即为19300 ,这是因为包含了客户端B的600. 从客户的角度看,客户看不到客户B. 这会感觉到这个派是世界之外的,还有600多块. 这是幻影阅读. 从开发人员的角度来看,数据的一致性并未受到破坏. 但是在应用程序中,我们的代码可能会向用户提交18700. 如果必须以小概率避免这种情况,则必须采用下面描述的事务隔离级别“序列化”
mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 18700 |
+--------------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 19300 |
+--------------+
1 row in set (0.00 sec)
4. 序列化
(1)打开一个客户端A,并将当前事务模式设置为可序列化,查询表帐户的初始值:
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+------+--------+---------+
| id | name | balance |
+------+--------+---------+
| 1 | lilei | 10000 |
| 2 | hanmei | 10000 |
| 3 | lucy | 10000 |
| 4 | lily | 10000 |
+------+--------+---------+
rows in set (0.00 sec)
(2)打开客户端B并将当前事务模式设置为可序列化. 插入一条记录并报告错误. 该表已锁定. 插入失败. 当mysql中的事务隔离级别可序列化时,该表将被锁定. 在这种情况下,此隔离级别的并发性非常低. 通常,一个事务占用一张桌子. 其他数万笔交易只能在等待他完成提交后才能使用. 它很少在开发中使用.
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(5,'tom',0);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
补充:
1. SQL规范规定的标准,不同的具体实现可能有所不同
2. mysql中的默认事务隔离级别是可重复读取,并且不锁定读取行
3. 当事务隔离级别被序列化时,读取数据将锁定整个表
4. 阅读本文时,如果您站在开发人员的角度,您可能会感觉到没有可重复的阅读和幻像阅读. 没有逻辑问题. 最终数据仍然是一致的,但是从用户的角度来看. 他们通常只看到一个事务(仅看到客户端A,不知道客户端B的秘密存在),并且不考虑事务并发执行的现象. 一旦相同的数据出现多次,结果有所不同,或者新记录突然出现,他们可能会怀疑,这是用户体验的问题.
5. 当事务在mysql中执行时,最终结果将不会出现数据一致性问题,因为在事务中,mysql执行某个操作可能不会使用前一个操作的中间结果,这将基于其他实际情况处理并发事务,这似乎是不合逻辑的,但是它保证了数据的一致性;但是当在应用程序中执行交易时,下一个操作将使用一个操作的结果,并且将执行其他计算. 这是我们必须要小心的事情. 重复读取时应锁定行,序列化期间必须锁定表,否则数据一致性将被破坏.
6. 当事务在mysql中执行时,mysql会根据每个事务的实际情况进行全面处理,导致数据的一致性并没有被破坏,而是应用程序按照逻辑例程来打牌,而mysql没有聪明,这是不可避免的. 存在数据一致性问题.
7. 隔离级别越高,就可以保证越多的数据完整性和一致性,但是对并发性能的影响越大,鱼爪和熊掌不能兼得. 对于大多数应用程序,您可以优先将系统的隔离级别设置为“ Read Committed”,这可以避免脏读并具有良好的并发性能. 尽管它将引起并发问题,例如不可重复的读取和幻像读取,但是在个别情况下,可能会出现此类问题,但应用程序可以由悲观锁或乐观锁来控制.
以上是本文的全部内容,希望对大家的学习有所帮助,并希望您支持脚本编写室.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-190492-1.html