分布式事务之浅谈数据库事务

事务是什么

是以一种可靠一致的方式,访问和操作数据库中的程序单元。

事务的ACID原则

  • 原子性 :事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性:事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性:多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性:一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。
举例一

用一个常用的“A账户向B账号汇钱”的例子来说明如何通过数据库事务保证数据的准确性和完整性。

  • A账户与B账户初始余额各为500元。
  • 对A账户做减法操作(500-100)。
  • 对B账户做加法操作(500+100)。

原子性:
保证所有过程要么都执行,要么都不执行。一旦在执行某一步骤的过程中发生问题,就需要执行回滚操作。 假如执行到B账户做加法操作时,B账户突然不可用(比如被注销),那么之前的所有操作都应该回滚到执行事务之前的状态。

一致性:
在转账之前,A和B的账户中共有500+500=1000元钱。在转账之后,A和B的账户中共有400+600=1000元。也就是说,数据的状态在执行该事务操作之后从一个状态改变到了另外一个状态。

隔离性:
在A向B转账的整个过程中,只要事务还没有提交(commit),查询A账户和B账户的时候,两个账户里面的钱的数量都不会有变化。
如果在A给B转账的同时,有另外一个事务执行了C给B转账的操作,那么当两个事务都结束的时候,B账户里面的钱应该是A转给B的钱加上C转给B的钱再加上自己原有的钱。

持久性:
一旦转账成功(事务提交),两个账户的里面的钱就会真的发生变化(会把数据写入数据库做持久化保存)。

以上介绍完事务的四大特性(简称ACID),现在重点来说明下事务的隔离性,当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:

  • 脏读: 脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
    当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100元,对应SQL命令如下
update account set money=money + 100 where name=’B’; 
update account set money=money - 100 where name=’A’;

当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。

  • 不可重复读: 不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
    例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发生了不可重复读。
    不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

  • 幻读: 幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从1修改为2的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为1并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

事务的隔离级别

现在来看看MySQL数据库为我们提供的四种隔离级别:

  • Serializable (串行化): 可避免脏读、不可重复读、幻读的发生。
  • Repeatable read (可重复读): 可避免脏读、不可重复读的发生。
  • Read committed (读已提交): 可避免脏读的发生。
  • Read uncommitted (读未提交): 最低级别,任何情况都无法保证。

以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。

在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。

在MySQL数据库中查看当前事务的隔离级别:

 select @@tx_isolation;

在MySQL数据库中设置事务的隔离级别:

set  [glogal | session]  transaction isolation level 隔离级别名称;
set tx_isolation=’隔离级别名称’;
举例二

重点演示一下可重复读

### 事务t1
START TRANSACTION;
UPDATE account SET amount = amount - 100 WHERE username = 'A';
UPDATE account SET amount = amount + 100 WHERE username = 'B';
COMMIT;
### 事务t2
START TRANSACTION;
SELECT * FROM account;
SELECT * FROM account where username = 'A';
COMMIT;

假设A账户与B账户最初各有100元
此时开启了事务t2,执行第一条查询语句,如下

### 事务t2
START TRANSACTION;
SELECT * FROM account;

此时查询的A与B账户余额都是100元,还未执行到第二条查询语句的时候,若此时开启了事务t1,且事务t1已执行完成,已提交。
事务t2继续执行下一个查询语句,此时查询出A的余额应该是100,而不是0。事务t2提交之后,再查询A的余额才是0。
可重复读就是在一个事务之内,重复多次读取一条数据,前后查询的结果应该是一致的。

读未提交
事务t1执行到对A账户做减法操作,注意此时还未提交,事务t2查询出来的数据是事务t1未提交的数据,也就是发生了脏读。

读已提交
事务t2查询的数据,前后可能会不一致,例如事务t2第二次查询执行之前,事务t1已提交,此时查询A的余额就是0。意思说其他事务只要提交了,就可以读到。

串行化
就是事务的排队执行,等待前一个事务执行完成。

发布了7 篇原创文章 · 获赞 6 · 访问量 145

猜你喜欢

转载自blog.csdn.net/weixin_45319877/article/details/102176477