MySQL事务基础

in MySQL with 0 comment

记录一下关于MySQL的事务,为马上学习的分布式事务做下准备。

事务的ACID

事务具有4个特征,分别是原子性、一致性、隔离性和持久性。就是我们常说的ACID。

原子性(atomicity)

一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性。
详细解释,就是,在开启一条事务的时候。在提交前,全部的操作,就是一个原子操作。也就是满足事务的原子性。

START TRANSACTION
UPDATE 	transaction_test set money = money -100 WHERE name = 'zs'
UPDATE 	transaction_test set money = money +100 WHERE name = 'ls'
COMMIT

如上面这串SQL,如果完整的执行,首先开启事务,然后commit前的两个update操作,就是满足一个原子操作。

一致性(consistency)

事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。

这个意思,在我们执行查询时,都是一个事务session,在执行前,session中的数据是和数据库的数据是一致的。在session执行提交后,session中的数据也应该和数据库的数据是一致的。

如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态。

隔离性(isolation)

事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。

不同的事务是不相互影响的。
MySQL定义了四个隔离级别,等会重点会讲。

持久性(durability)

一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。

就是对应的一致性,在事务提交后,数据应该永久的保存在数据库中。即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态

事务的隔离级别

在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同,分别是:未提交读,已提交读,可重复读和串行化。

四个隔离级别对应的规范也不同。

如果不考虑事务的隔离性,会发生脏读、不可重复读、幻读。

准备数据

测试数据
image.png
老生长谈的转账问题。

未提交读(read uncommitted)

读取未提交的数据。在这个隔离级别最常说的问题就是脏读问题。这个隔离级别发生的问题还有不可重复读和幻读问题。

设置隔离级别为未提交读

set session transaction ISOLATION level READ UNCOMMITTED;

查看当前session的隔离级别

select @@tx_isolation;

image.png

模拟操作

将隔离级别设置为未提交读后,进行模拟操作。
image.png

SQL语句如下:
zs转账给ls

START TRANSACTION
UPDATE 	transaction_test set money = money -100 WHERE name = 'zs'
UPDATE 	transaction_test set money = money +100 WHERE name = 'ls'
ROLLBACK
COMMIT

ls给自己充值

START TRANSACTION
SELECT * FROM Transaction_test WHERE name = 'ls';
# 代码层面实现ls充值
COMMIT

执行SQL操作

  1. 先执行zs给ls转账的操作,但是现在还没提交事务
    image.png
  2. 此时ls再去做充值的操作。
    1. ls先去看余额
    	SELECT * FROM Transaction_test WHERE name = 'ls';
    
    1. 发现余额是200
      image.png
      现在按照事务的隔离性已经出现问题了。因为zs的转账操作还没提交,但是ls的另外一个查询余额的事务可以看到zs未提交的数据。
    2. zs因为网络问题,事务回滚了。
      回滚后,zs的余额应该是100,ls的余额也是100
    3. 业务操作,ls给自己充值100
      因为ls现在查询到的值是200,但是实际上,ls的钱只有100(因为上一步回滚了),业务操作代码又给ls充值了100,代码查询到的数据库的值200,然后加上100,写回数据库。这样就造成了ls多出来了100。

ls在充值操作中的拿到的200那个余额数据就是一条脏数据,因为在zs转账的事务回滚了。

已提交读(read committed)

读取已经提交的数据,可以解决脏读,但是会发生不可重复读的问题。出了不可重复读问题,还有幻读的问题。
还是一样,开启两个session。

设置隔离级别

set session transaction ISOLATION level READ COMMITTED;

不可重复读。。想不出来什么业务场景。。
直接演示吧。。。

两条事务操作

事务一:

START TRANSACTION
UPDATE 	transaction_test set money = money -100 WHERE name = 'zs';
UPDATE 	transaction_test set money = money +100 WHERE name = 'ls';
COMMIT

事务二:

START TRANSACTION
SELECT * FROM Transaction_test;
SELECT * FROM Transaction_test;
COMMIT

事务一就是修改两条事务
事务二就是查询两次数据

不可重复的问题,就是发生在事务二中,两次查询数据中间,另外一个事务修改并提交了事务。

效果演示

  1. 开启两条事务
  2. 事务二先查询一次
    image.png
  3. 事务一在进行修改数据的操作,此时先不提交
    image.png
  4. 事务二再去查询一遍
    image.png
    数据没变,说明没发生脏读的问题。
  5. 事务一提交,事务二再去查询
    image.png
    不可重复读问题出现。在同一个事务中(事务二还未提交),两次重复读取的数据不一致。

可重复读(repeatable read)

重读读取:可以解决脏读和不可重复读但是会发生幻读问题。
该隔离级别生MySQL默认的隔离级别。
幻读就不演示了。
幻读指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

串行化(serializable)

可以解决脏读不可重复读和幻读。
串行化相当于锁表。

总结

在MySQL中事务就是ACID。
事务隔离级别就是隔离性的体现。他们中发生不同的问题就如下表

隔离级别脏读不可重复读幻读
未提交读
已提交读X
可重复读XX
串行化XXX