数据库事务概述

一、数据库事务

概述

我们的数据库一般都会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增 删改查操作,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。 这些问题的本质都是数据库的多事务并发问题,为了解决多事务并发问题,数据库设计了事 务隔离机制、锁机制、MVCC多版本并发控制隔离机制、日志机制,用一整套机制来解决多 事务并发问题。接下来的,我们会深入讲解这些机制,让大家彻底理解数据库内部的执行原理。

把多条语句作为一个整体进行操作的功能,被称为数据库事务。数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。如果事务失败,那么效果就和没有执行这些SQL一样,不会对数据库数据有任何改动。

数据库事务具有ACID这4个特性:

  • (A)Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行,原子性由undo log日志来实现

  • (C)Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100,使用事务的最终目的,由其他三个特性以及业务代码正确逻辑来实现

  • (I)Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离,内部操作不能互相干扰,隔离性由MySQL的各种以及MVCC机制来实现

  • (D)Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。表示它对数据库的改变就应该是永久性的,持久性由redo log日志来实现

    对于单条SQL语句,数据库系统自动将其作为一个事务执行,这种事务被称为隐式事务。

要手动把多条SQL语句作为一个事务执行,使用BEGIN开启一个事务,使用COMMIT提交一个事务,这种事务被称为显式事务,例如,把上述的转账操作作为一个显式事务:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

数据库事务是由数据库系统保证的,我们只需要根据业务逻辑使用它就可以。

二、隔离级别

“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定 的事务隔离机制来解决。

对于两个并发执行的事务,如果涉及到操作同一条记录的时候,可能会发生问题。因为并发操作会带来数据的不一致性,包括脏读不可重复读幻读等。数据库系统提供了隔离级别来让我们有针对性地选择事务的隔离级别,避免数据不一致的问题。

并发事务处理带来的问题

  • 更新丢失(Lost Update)或脏写 当两个或多个事务选择同一行数据修改,有可能发生更新丢失问题,即最后的更新覆盖了由其他事务所做的更新。
  • 脏读(Dirty Reads) 事务A读取到了事务B已经修改但尚未提交的数据
  • 不可重读(Non-Repeatable Reads) 事务A内部的相同查询语句在不同时刻读出的结果不一致
  • 幻读(Phantom Reads) 事务A读取到了事务B提交的新增数据
隔离级别脏读(Dirty Reads)不可重复读(Non-Repeatable Reads)幻读(Phantom Reads)脏写(Lost Update)
读未提交(Read uncommitted)可能可能可能可能
读已提交(Read committed)不可能可能可能可能
可重复读 (Repeatableread)不可能不可能可能可能
可串行化 (Serializable)不可能不可能不可能不可能

SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:

查看当前数据库的事务隔离级别: show variables like ‘tx_isolation’;

设置事务隔离级别:set tx_isolation=‘REPEATABLE-READ’;

1、读未提交(read uncommit)——>脏读
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。

set tx_isolation=‘READ-UNCOMMITTED’;

2、读已提交(read commit)——>不可重复读(Oracle默认的隔离级别)

读提交,顾名思义,就是只能读到已经提交了的内容

set tx_isolation=‘READ-COMMITTED’;

3、可重复读(repeatable read) ——> 幻读(MySQL默认隔离级别)

可重复读,顾名思义,就是专门针对“不可重复读”这种情况而制定的隔离级别,自然,它就可以有效的避免“不可重复读”。而它也是MySql的默认隔离级别。

set tx_isolation=‘repeatable‐read’;

:以上两种用的普遍比较多

4、串行(serializable) ——> 解决上面所有问题,包括肮写(隔离级别最高)

set tx_isolation=‘serializable’;

Serializable是最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。

虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。

如果没有指定隔离级别,数据库就会使用默认的隔离级别。在MySQL中,如果使用InnoDB,默认的隔离级别是Repeatable Read(可重复读)。

InnoDB引擎中,定义了这四种隔离级别供我们使用,级别越高事务隔离性越好,担性能就越低,而隔离性是由MySQL的各种锁以及MVCC机制来实现

案例分析

CREATE TABLE `account` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) DEFAULT NULL,
 `balance` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 INSERT INTO `test`.`account` (`name`, `balance`) VALUES ('lilei', '450');
 INSERT INTO `test`.`account` (`name`, `balance`) VALUES ('hanmei', '16000');
 INSERT INTO `test`.`account` (`name`, `balance`) VALUES ('lucy', '2400');
读未提交

(1)打开一个客户端A,并设置当前事务模式为read uncommitted(读未提交),修改表 account的初始值

在这里插入图片描述

(2)在客户端A的事务提交之前,打开另一个客户端B,并查询account表初始值

(3)这时,虽然客户端A的事务还没提交,但是客户端B就可以查询到A已经更新的数据

在这里插入图片描述

(4)一旦客户端A的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端B查询到的数据其实就是脏数据

读已提交

(1)打开一个客户端A,并设置当前事务模式为read committed(读已提交),修改表 account的初始值

在这里插入图片描述

(2)在客户端A的事务提交之前,打开另一个客户端B,并查询account表初始值,并没有查到客户端A修改的数据(解决脏读问题),只有当客户端A的事务提交之后,才能查到客户端A修改的数据,这就叫读已提交

(3)会产生不可重复读的问题,假如在开启一个事务修改account表值,此时在客户端B读到的数据会产生不一致的问题,也就是说这个事务被别的事务影响了,也就是说隔离性不是特别好

在这里插入图片描述

可重复读

(1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的所有记录

在这里插入图片描述

(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交

在这里插入图片描述

(3)在客户端A查询表account的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题

在这里插入图片描述

(4)在客户端A,接着执行update account set balance = balance + 500 where id = 1, balance没有变成1000+500=1500,lilei的balance值用的是步骤2中的1500来算的,所以是 2000,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC(multi-version concurrency control)机制

  • select操作是快照读(历史版本)
  • insert、update和delete 是当前读(当前版本)

在这里插入图片描述

注: update 语句会加行锁,当前事务未提交,其他事务不可操作

  • 解决脏写还可以使用乐观锁的方式
    • 首先业务表加上version字段,每次做跟新操作加1;
    • update条件语句加上版本字段条件(与查询sql语句版本是否一致)
    • 不一致(证明其他事务已经修改并提交),可进行重试

CAS----> 先比较在交换数据再设置值

(5)在当前事务没有更新语句查询结果一致【还是查询快照】(其他事务中对account 表新增数据并提交),但是当前事务出现更新操作,此时没有真正解决幻读**,再次查询能查到其他事务新增的数据。

  • 可重复读结合间隙锁可解决幻读
串行化

(1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值

(2)打开一个客户端B,并设置当前事务模式为serializable,更新相同的id为1的记录会被 阻塞等待,更新id为2的记录可以成功,说明在串行模式下innodb的查询也会被加上行锁, 如果查询的记录不存在会给这条不存在的记录加上锁(这种是间隙锁,后面会详细讲)。 如果客户端A执行的是一个范围查询,那么该范围内的所有行包括每行记录所在的间隙区间 范围都会被加锁。此时如果客户端B在该范围内插入数据都会被阻塞,所以就避免了幻读。 这种隔离级别并发性极低,开发中很少会用。

锁的分类

  • 读锁(共享锁、S锁):select … lock in share mode;

    读锁是共享的,多个事务可以同时读取同一资源,但不允许其他事务修改

  • 写锁(排他锁、X锁):select … for update;

    写锁是排他的,会阻塞其他的写锁和读锁,update、delete、insert都会加写锁

MVCC机制

MVCC(Multi-Version Concurrency Control)多版本并发控制,就可以做到读写不阻塞,且避免了类似脏读这样的问你题,主要通过undo日志链来实现

  • select操作是**快照读**(历史版本)
  • insert、update和delete是**当前读**(当前版本)
  • read commit(读已提交),语句级快照
  • repeatable read(可重复读),事务级快照

trol)多版本并发控制`**,就可以做到读写不阻塞,且避免了类似脏读这样的问你题,主要通过undo日志链来实现

  • select操作是**快照读**(历史版本)
  • insert、update和delete是**当前读**(当前版本)
  • read commit(读已提交),语句级快照
  • repeatable read(可重复读),事务级快照
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值