事务与并发,隔离

218 阅读7分钟

一. 什么是事务

常见的MySQL存储引擎中只有InnoDB是支持事务的。

事务就是现实中抽象出来一种逻辑操作,要么都执行,要么都不执行,不能存在部分执行的情况。一个完整的业务逻辑。

事务的实现通过事务日志来实现。

比较经典的案例就是银行转账:小A向小B转账100元

正常的情况:小A的账户扣减100元,小B的账户增加100元。这就是完整的业务逻辑,最小的工作单元,不可以再分,同时成功,或者同时失败。

非正常情况: 小A的账户扣减100元,小B账户金额不变。

非正常情况下,小A账户扣减100之后银行系统出现问题,小B账户增加100元的操作并没有执行。也就是两边金额对不上了,小A不愿意,小B不愿意,银行也不愿意啊。事务的出现就是为了避免非正常情况的出现,让大家都满意。

二. 事务的4大特性(ACID)

1. 原子性(Atomicity)

事务的操作是不可分割的,要么都操作,要么都不操作,就像转账一样,不存在中间状态。而且这个原子性不是说只有一个动作,可能会有很多的操作,但是从结果上看是不可分割的,也就是说原子性是一个结果状态。

2. 一致性(Consistency)

执行事务的前后,数据保持一致,同时成功,或者同时失败,就像银行账户系统一样无论事务是否成功,两者的账户总额应该是一样的。

3. 隔离性(Isolation)

多个事务同时操作数据的时候,多个事务直接互相隔离,不会互相影响。

4. 持久性(Durability)

一个事务在提交后对数据的影响是永久的,写入磁盘中不会丢失。

三. 显式事务、隐式事务

mysql的事务分为显式事务隐式事务

默认的事务是隐式事务(每次执行DML语句后自动提交事务),由变量autocommit控制 在操作的时候会自动开启,提交,回滚。

set autocommit=0; -- 关闭自动提交事务(显式)

set autocommit=1; -- 开启自动提交事务(隐式)

-- 当autocommit=1的时候自动控制事务

执行一条语句就自动提交一次事务

-- 当autocommit=0 手动提交事务

start transaction; -- 开启事务,等同于set autocommit=0;

commit; -- 提交事务

rollback; -- 回滚事务

SAVEPOINT 保存点名称;  -- 保存点(相当于存档,可以不用回滚全部操作)

rollback to  保存点;  -- 回滚到某个保存点 ,这样很多操作的时候可以只回滚部分

四. 并发事务中的问题

如果对表的操作同一时间只有一个事务就不会有问题,但是这是不可能的。现实中都是尽可能的利用,多个事务同时操作。多个事务就会带来不少的问题,例如脏读脏写不可重复读幻读

1. 脏读

一个事务读取到另一个未提交事务修改后的数据 这就是脏读。

例如两个事务a,b: 同时操作一条记录

a事务修改记录后还没有正式提交到数据库,这时b事务去读取,然后用读取到的数据进行后续操作。

如果a事务回滚了,这个修改后的数据就不存在了,那么b事务就是在使用一个不存在的数据。这种就是脏数据。

2. 脏写(数据丢失)

一个事务修改了另一个未提交事务修改过的数据

例如两个事务a,b: 同时操作一条记录

a事务修改后没有提交, 接着b事务也修改同一条数据,然后b事务提交数据。

如果a事务回滚自己的修改,同时也把b事务的修改也回滚了,造成的问题就是:b事务修改了 也提交了,但是数据库并没有改变,这种情况就是脏写。

3. 不可重复读

一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。

也就是在同一个事务中多次读取同一条记录,得到的内容都不一样(在每次读取之前都有其他事务完成修改并提交),这就是不可重复读

4. 幻读

在一个事务内相同条件查询数据,先后查询到的记录数不一样

也就是一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来,那就意味着发生了幻读

不可重复读和幻读的区别:不可重复读重点在于同一条记录前后数据值不一样(内容的变化),而幻读重点在于相同查询条件前后所获取的记录数不一样(条数的变化)

五. 事务的隔离级别

上面说的事务的并发问题,在不同的场景下要求不一样,能接受的问题也不一样。他们之间的严重性排序如下:

脏写 > 脏读 > 不可重复读 > 幻读

SQL中提供了4种隔离级别来处理这几个问题,如下:

  • READ-UNCOMMITTED(读未提交) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读不可重复读幻读。但是并发度最高
  • READ-COMMITTED(读已提交) :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读) :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读不可重复读,但幻读仍有可能发生。类似读取拷贝。
  • SERIALIZABLE(可串行化) :最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,该级别可以防止脏读不可重复读以及幻读。并发度也是最低的

MySQL默认采用的 REPEATABLE_READ 隔离级别

Oracle默认采用的 READ_COMMITTED 隔离级别

  1. 如何设置隔离级别

可以通过变量参数transaction_isolation 查看隔离级别

mysql> SELECT @@transaction_isolation;

+-------------------------+

| @@transaction_isolation |

+-------------------------+

| REPEATABLE-READ         |

+-------------------------+

1 row in set (0.00 sec)



mysql> show variables like '%transaction_isolation%';

+-----------------------+-----------------+

| Variable_name         | Value           |

+-----------------------+-----------------+

| transaction_isolation | REPEATABLE-READ |

+-----------------------+-----------------+

1 row in set, 1 warning (0.04 sec)

修改的命令:SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;

level的值就是4种隔离级别

READ-UNCOMMITTED

READ-COMMITTED

REPEATABLE-READ

SERIALIZABLE

1. 设置全局隔离级别

只对执行完该语句之后产生的会话起作用。

当前已经存在的会话无效,包括执行这条语句的会话。

set global transaction_isolation='read-uncommitted';

set global transaction_isolation='read-committed';

set global transaction_isolation='repeatable-read';

set global transaction_isolation='serializable';

2. 设置会话的隔离级别

对当前会话的所有后续的事务有效

该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务。

如果在事务之间执行,则对后续的事务有效。

即对新建会话无效

set session transaction_isolation='read-uncommitted';

set session transaction_isolation='read-committed';

set session transaction_isolation='repeatable-read';

set session transaction_isolation='serializable';

www.jianshu.com/p/182314a64…