数据库事务

数据库的事务和spring中的事务类似,因为spring中的事务依赖数据库中事务机制,本文尽量不介绍重复部分

大家可以先去了解一下我之前写的spring的事务:https://blog.csdn.net/w8827130/article/details/84499213

我所使用的是mysql数据库,所以本文围绕mysql来说。

数据库事务介绍:

数据库中ACID对应着事务的四个特性:原子性、一致性、隔离性和持久性。

事务中存在着一些问题:脏读、不可重复读和幻读,所以存在着不同的事务的隔离等级来处理这些问题。

事务的隔离等级有read uncommitted(读未提交)、read committed(读已提交)、repeatable read(可重复读)、serializable(串行化)。

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

MySql5.5.5版本以上默认Engine是InnoDB,其他版本默认是MyISAM。

show variables like '%version%';查看本机mysql版本,我本机的mysql版本是5.6.26的。

只有InnoDB提供事务的支持,在mysql控制中输入show engines;可以查看当前mysql版本支持哪些存储引擎。不同的存储引擎可以按具体需求在不同的业务场景中使用,并不是规定死了只能用InnoDB。

show variables like '%isolation%';查看到当前版本mysql的事务隔离等级,默认为可重复读(repeatable read)

事务的隔离等级:

read uncommitted(读未提交)

read uncommitted有三个问题,脏读、不可重复读和幻读。

让我们来看看事务的脏读是如何进行的。

首先创建新表和插入数据

/* 创建账号表 */
create table account 
(
    id int primary key auto_increment,
    name varchar(40),
    money float
) character set utf8 collate utf8_general_ci;

/* 插入测试数据 */
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000); 

查看刚刚生成的account表和其中的数据,select * from test.account;(test.account是数据库名.表名)

set session transaction isolation level read uncommitted;(设置当前窗口会话的本机mysql事务隔离机制为read uncommitted)

打开窗口A和窗口B两个mysql控制台并且设置两个窗口的isolation level为read uncommitted(左边窗口为A窗口,右边窗口为B窗口

开启窗口A和B的事务start transaction;

事务A开始更新数据,更新account表中name为aaa用户的money字段,减100。这个时候事务A可以读取到自己事务还未提交的数据,name为aaa的账号的money变成了900,事务B也可以查询到事务A中未提交的数据。

这时候事务A还未提交,navicatForMysql中account表里name为aaa账号的money字段依然是事务A更新之前的1000,并没有变成900。

这时候将事务A进行回滚(rollback;),数据回滚了,又变回了1000,而在这之前事务B读到的事务A未提交成功的数据就变成了脏数据,这就是脏读。

 

read committed(读已提交)

先查看窗口A和窗口B的account表的初始数据

把窗口A和B的isolation level改为read committed(set session transaction isolation level read committed;)

然后再来试试能不能进行脏读

在isolation level为read committed级别下,事务B未提交的数据,事务A读取不到,所以解决了脏读的问题。

但是read committed级别下还有不可重复读和幻读的问题,现在来重现一下不可重复读的问题。

在事务B提交事务之后,name为aaa的money变成了900,而这时候事务A还未完成,它读取到了事务B已提交的数据,aaa的money从1000变成了900,前后读取的数据不一致,这就是不可重复读。

repeatedable read(可重复读)

可重复读的隔离等级下,解决了脏读和不可重复读的问题,但是还会出现幻读的问题。

set session transaction isolation level  repeatable read;(设置数据库会话中的事务隔离等级为可重复读)

在事务A未提交前,name为aaa的money一直是900,事务B已经将money改为800并且提交了事务,这时候数据库中money已经是800了,这时候事务A中的aaa的money仍然是900,没有出现不可重复读的问题,在可重复读的隔离等级下可以解决不可重复读的问题。

让我们来看看幻读是怎样的?

事务B插入一条数据后提交事务,而在repeatable read隔离级别下,事务A依然是可重复读的,所以事务A前后读到的数据行并没有变化,但是数据库中已经多了一条数据了。

这时候在事务A中同样也插入id为4的数据行,就会报错,ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY',我明明在事务A中没有查到id为4的数据行,居然报错说主键冲突,然后再更新所有的数据行的money为1000,更新完了再进行查询发现多了一条数据,这就是幻读。

serializable(串行化)

串行化的隔离等级下,解决了脏读、不可重复读和幻读的问题。

set session transaction isolation level  serializable;(设置数据库会话中的事务隔离等级为串行化)

同时开启事务A和事务B,然后事务A和事务B准备向数据库中插入一条数据,这时候会发现控制台并没有迅速做出反应,而是一直在尝试获取锁,最后报错提示获取锁的时候超时,尝试重启事务,好像进入了死锁。

在事务B发生错误后,事务A也插入同样的数据,也是一样的超时报错,然后我回滚了事务B,这时候事务A再进行插入操作就成功了,这个就直接限制了同一时间只能有一个事务。

但是在事务B插入操作还未超时之前事务A也插入相同的数据的时候,mysql就会检测到死锁,将事务A报错,而事务B插入成功了,删除和修改也是相同的结果,也就是说在serializable隔离级别下,增删改行为不能出现两个事务同时操作同一张表的情况,而读不受限制,这就避免了脏读、不可重复读和幻读,serializable是最安全的数据库事务隔离级别。

总结

可重复读:行锁(事务过程中读取某行的数据不变,因为该行被锁了)

串行化:锁表(一个事务开始执行,就将表锁住,事务执行完再释放锁)

数据库事务并发时会出现一些问题,脏读、不可重复读和幻读,而数据库也有相应的数据库事务隔离级别来相应的解决这些问题,但是世间安得双全法,不负如来不负卿,想要更高的数据安全级别,就需要拿数据库性能来换,更高的事务隔离级别意味着更低的事务并发数,而且很容易发生死锁情况,所以我们一般默认的事务隔离级别是repeatable read。当然也可以根据业务需求去选择更适合自己系统的事务隔离级别,一直都是没有什么最好,只有更合适。

至于数据库的事务底层是如何实现的,放在下一篇博客学习,避免过长,看的各位大佬眼睛疼。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值