抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

🈲🈲Ctrl + z Ctrl + Y🈲🈲

   好不容易开开心心撸完这篇博客,一下子Ctrl Z,手忙脚乱,啪,没了😔😔。淦,这是二次回放。第一次的思维已经莫得了。。。谨慎撤销、谨慎撤销、谨慎撤销

This is a picture without description


   为什么需要事务?考虑一个场景——转账。账户A有100块钱,给账户B转50。其中就包含着两个有先后顺序的 update 操作。正常情况下,第一个update,账户A 100-5050。同时账户B update50。总的金额还是100,保持不变。如果出现异常,在第一次 update 执行结束后,第二个update 执行失败,那么账户A 100-5050。但账户B并没有加50。此时金额的总量由100变成了50。这是无法忍受的,那么就需要事务处理机制来解决。

一、事务

事务处理(transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。

用事务思维思考上述场景:如果转账的两个操作能够要么完全执行,要么完全不执行,还会出现金额丢失的情况吗?不会,结果要么正常转账,要么转账失败,但金额不变。这两个update就是事务。由此,可以进一步探究事务的特性👇👇。

ACID特性

四个特性ACID

  • **原子性(Atomicity)**:事务的整个过程如原子操作一样,最终要么全部成功,或者全部失败。

  • **一致性(Consistency)**:一个事务必须使数据库从一个一致性状态变换到另一个一致性状态。即达到期望。

  • **隔离性(Isolation)**:一个事务的执行不能被其他事务干扰。

  • **持久性(Durability)**:一个事务一旦提交,他对数据库中数据的改变就应该是永久性的。当事务提交之后,数据会持久化到硬盘,修改是永久性的。

初步认识了事务的来源背景和事务特性,再来在看看数据库中怎么用,增强认识。

二、显隐式事务

隐式事务

MySQL 中事务默认是隐式事务,执行insertupdatedelete操作的时候,数据库自动开启事务、提交或回滚事务

显式事务

很明显,显式事务就是需要手动开启、提交或回滚,由开发者自己控制。

三、事务操作

1. 操作术语

  • 事务(transaction) 指一组SQL语句;
  • 回退(rollback) 指撤销指定SQL语句的过程;
  • 提交(commit) 指将未存储的SQL语句结果写入数据库表;
  • 保留点(savepoint) 指事务处理中设置的临时占位符(placeholder),你可以对它发布回退(与回退整个事务处理不同)。

否开启隐式事务是由变量autocommit控制的。为ON表示开启了自动提交。
语法:

1
show variables like 'transaction_isolation';

2. 开启事务

方式1:

1
2
3
4
5
6
7
//设置不自动提交事务
set autocommit=0;

//执行事务操作

//事务结束:提交或回滚
commit|rollback;

方式2:

1
2
3
4
5
6
// 开启事务
start transaction;

// 执行事务操作

commit|rollback;
开启事务示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 1 warning (0.10 sec)

mysql> show variables like "autocommit";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set, 1 warning (0.01 sec)

mysql> set autocommit = 1;
Query OK, 0 rows affected (0.14 sec)

mysql> show variables like "autocommit";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)

3. 保留点

如果发生故障时不需要全部回退,而只选择性的回退一部分,则可以使用保留点,回退到指定的点。

1
2
SAVEPOINT 保留点名;
ROLLBACK TO 保留点名;

4. 只读事务

1
start transaction read only;

开启了之后,执行更改数据的命令会出错。

进一步解析事务执行

1
2
3
4
5
6
7
8
// 开启手动操作事务
set autocommit = 0;

// 事务操作:insert \ update \ delete
// 执行了事务之后,数据库中的表已经发生了修改了。意味着其他事务可以读取数据来使用。这也就导致问题的出现

// 执行提交或回滚,执行完之后事务才算结束。
commit | rollback;

可见,在事务中一旦执行更新表操作,表中的数据就会变化。而此时其他事务是可以读取表中的数据来使用的,而一旦该事务不提交而回滚了,那么其他事务读取到的将是错误的数据。由此,说明事务是存在问题的。

四、事务中存在的问题

  1. 脏读

一个事务在执行的过程中读取到了其他事务还没有提交的数据。

  1. 读已提交

一个事务操作过程中可以读取到其他事务已经提交的数据。
事务中的每次读取操作,读取到的都是数据库中其他事务已提交的最新的数据(相当于当前读)

  1. 可重复读

一个事务操作中对于一个读取操作不管多少次,读取到的结果都是一样的。

  1. 幻读

幻读发生在可重复读的基础上。事务中后面的操作需要前面的读取操作提供支持,但读取操作却不能支持下面的操作时产生的错误,就像发生了幻觉一样。

比如:

事务A:1.读取某条记录—发现不存在;2.那么事务A进行插入—结果插入失败;3.再次读取该条记录—还是发现不存在——事务A纳闷发生了什么?(幻读-事务A明明没有读到该条记录啊)

事务B:1.在事务A读取之后,插入之前,事务B插入了该条数据——导致事务A插入失败;2.但事务A再次读的时候还是不存在(可重复读)

针对上面这些问题:如何保证事务中数据的正确性——一个事务是否可以看见另一个事务已提交或未提交的数据?——通过事务的隔离级别来解决。

五、隔离级别

事务隔离级别主要是解决了上面多个事务之间数据可见性及数据正确性的问题。

隔离级别相当于数据库环境的状态,不同的状态有不同的功能效果。

隔离级别分为4种:

  • 读未提交:READ-UNCOMMITTED

  • 读已提交:READ-COMMITTED

  • 可重复读:REPEATABLE-READ

  • 串行:SERIALIZABLE

4中隔离级别越来越强,会导致数据库的并发性也越来越低。

查看隔离级别:show variables like ‘transaction_isolation’;

设置隔离级别

  1. 修改mysql中的my.init文件
1
2
3
# 隔离级别设置,READ-UNCOMMITTED读未提交, READ-COMMITTED读已提交,
# REPEATABLE-READ可重复读, SERIALIZABLE串行
transaction-isolation=READ-UNCOMMITTED
  1. 重启mysql。

各种隔离级别中会出现的问题

隔离级别 脏读 不可重复读 幻读
READ-UNCOMMITTED
READ-COMMITTED
REPEATABLE-READ
SERIALIZABLE 无

1. 隔离级别为——读未提交

场景模拟(空表,两个事务,按序号顺序):

事务A:1. 开启事务,然后读取——空表;4.读取——成功,事务B插入的数据;6.commit;

事务B:2.开启事务,然后插入数据——成功;3.读取——成功,有数据;5. commit;

结论:

  • 读未提交情况下,可以读取到其他事务还未提交的数据,出现了脏读;
  • 多次读取结果不一样,为不可重复读。

2. 隔离级别为——读已提交

场景模拟(空表,两个事务,按序号顺序):

事务A:1.开启事务,然后读取——空表;4. 读取数据——还是空表;6. 读取数据——有事务B插入的数据;7.commit;

事务B:2.开启事务,然后插入——成功;3.读取数据——成功;5. commit;

结论:

  • 读已提交情况下,无法读取到其他事务还未提交的数据,未出现脏读;
  • 可以读取到其他事务已经提交的数据,出现了读已提交;
  • 多次读取结果不一样,不可重复读。

3. 隔离级别为——可重复读

场景模拟(空表,两个事务,按序号顺序):

事务A:1.开启事务,然后读取——空表;3.读取数据——还是空表;5.读取数据——还是空表;6.commit;7. 读取数据——有事务B插入的数据;

事务B:1.开启事务,插入数据——成功;2.读取数据——成功;4.commit;

结论:
可重复读的情况下,无法读取到其他事务还未提交的数据,未出现脏读;
未读取到其他事务已经提交的数据,且多次读取结果一直,为可重复读;

4. 隔离级别为——可重复读——幻读

场景模拟(空表,两个事务,按序号顺序):

事务A:1.开启事务,然后读取某条记录——发现不存在;4.进行该记录插入——结果插入失败;5.再次读取该条记录——还是发现不存在啊——事务A纳闷发生了什么?第一次读取是发生了幻觉?(幻读-事务A明明没有读到该条记录啊)

事务B:2.开启事务,插入了该条数据——成功;3.读取该条记录——成功;6.commit;

结论:

事务A第一次读取的时候相当于幻觉,即幻读。明明没这条数据,但插入还是失败,第二次再读取来确认,发现确实是不存在的呀,晕。——第二次读还是不存在,得益于可重复读级别。

5. 隔离级别为——串行

串行是针对整个事务而言的。一个事务想要执行,必须等待上一个事务执行结束才可以执行。

结论:一个个事务串行执行,不存在脏读、不可重复读、幻读的问题。但效率较低。

六、关于隔离级别的选择

  1. 需要对各种隔离级别产生的现象非常了解,然后选择的时候才能游刃有余
  2. 隔离级别越高,并发性也低,比如最高级别SERIALIZABLE 会让事物串行执行,并发操作变成串行了,会导致系统性能直接降低。
  3. 具体选择哪种需要结合具体的业务来选择。
  4. 读已提交(READ-COMMITTED)通常用的比较多。

吐槽:好好的一篇文章变成了流水账,可惜!!!💔💔写文章确实需要灵感,一件艺术品,总是需要精心雕刻的。

参考

评论

Gitalk评论系统对接至Github Issue,随心评论🐾🐾.....