事务的四大特性
- 原子性(atomicity) 我们经常说,一个事务执行失败了,就得回滚,其实这就是事务的原子性,一个完整事务,要么全部执行成功,如果有一个或者多个失败,那么就要回滚,其实这也是另一个特性即一致性的基础
- 一致性(consistency) 一致性,先举个栗子,最容易理解的栗子,本来刘备有200元,关羽没有钱,那么刘备给关羽转账100元,现在需要两步执行这个操作,先减去刘备账户里面的100元,第二步,给关羽账号增加100元,那么这两步算是一个事务。在事务发生前,关羽0 +刘备200=200元,事务结束后,关羽100+刘备100=200,数据没有平白增加或者减少。而且不管这兄弟俩怎么转,这个总和不会发生变化,这就是事务的一致性。一致性就是说事务必须使用数据库从一个一致性状态变换到另一个一致性状态。想想我们讲的第一个原子性,如果一半成功,一半失败而没有回滚,还会有数据的一致性吗?
- 隔离性(isolation) 隔离性主要是针对并发访问来讲的,当多个用户修改数据表时,数据库为每一个用户开启的事务,不能被其他的事务干扰,多个并发要互相隔离,即对于同一个资源,在同一个时间段只能有一个事务可以修改
- 持久性(durability) 持久性就是说一旦事务成功提交,那么对于数据库来说,这种改变是持久的
事务的隔离级别
在mysql中,支持四种隔离级别,即ru,rc,rr,serializealbe,后面我们会一一讲解,mysql默认支持的的rr,
-
此处给大家推荐一款好用的数据库管理工具,jetbrains出品的datagrip;好用哇哇!,下面的sql语句都是在datagrip中运行的
-
READ UNCOMMITTED(未提交读)
-
ru是指在一个事务执行未完成的时候,数据对其他事务也是可见的,而且读取的是已经更改但是还没有commit的数据,这种保证不了数据准确的隔离级别几乎是不用的。也正如此,所以它的性能是最优的
- 创建相关的数据库和数据表
create schema test;
use test;
CREATE TABLE `t` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`point` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
下面粘贴sql语句,注意在同一行的代码先左后右执行,是在两个sql窗口执行的,在执行到第6行的时候发现,console2开启了事务,但是还没有commit,但是console1已经查询到未提交的结果了。这就是读未提交
-
READ COMMITTED(读已提交) 大部分的数据库默认隔离级别就是rc,就是说一个事务只能看见已经提交的修改结果,如果没有提交,那么只能看见原来的结果,而不会看到修改未提交的结果,但是这会出现一个问题,什么问题呢?就是 在一个事务中,如果另一个事务修改了数据并且提交,那么在第一个事务中针对同一个查询,就会查询出来两个不同的数据,即不可重复读
-
新增一条如果没有提交也是读取不到的
-
这个时候,如果两个窗口同时对一条数据更改会发生什么情况呢?第二条更新命令会一直等第一条命令的提交,未提交就处于等待状态
-
REPEATABLE READ(可重复读) 可重复读是mysql默认的隔离级别,rr解决了脏读的问题,但是可能会出现幻读,下面先来演示一下幻读的实现
-
看一下已经解决了读未提交的问题和不可重复读的问题
-
幻读其实是解决不可重复读的一个缺点,为什么?首先事务1已经执行了一个插入操作,新增id3,但是为了可重复读,事务2看不到新增的数据,所以当事务2增加id3的时候报错,因为id3在数据表中已经切切实实的存在了。可重复读,有点像把某一个时刻的数据作为快照写入了缓存,在commit之前所有的读取都是源自缓存,而非真实的表
SERIALIZABLE(可串行化)
其实我们可以先自己想一下,如何在解决重复读的时候还能解决幻读呢?是不是感觉有点不可能,既然不幻读,那就实现不了可重复读,然鹅,但是,前面的操作都是基于两个事务,但是如我们把两个事务再关联一下呢,是不是就可以解决了,这就是串行的意思,可串行化解决了脏读,幻读,可重复读等问题,但是,势必会影响效率,"可串行化"会在读取的每一行数据上都加锁,所以可能会导致大量的锁等待和超时问题,所以在实际的生产环境中也很少会用到这个隔离级别,只有在非常需要确保数据的一致性切可以接受没有并发的情况下,才会考虑使用这个隔离级别。
- 演示一下,先看一下目前的情况
- 怎么实现串行化呢,说起来有点恶心,这次不是缓存快照了,让你读实时数据,但是更新操作我给你停了。我让你等到没有事务了,或者其他事务都提交了,才让你这个写操作执行,嗯,就是这样。感觉有点不太高明
- 用了锁,有人读也上锁,有人写也上锁,效率能没有影响吗,写之前先看有没有读锁,有读锁就等待。这种方式就是 简单,粗暴
- 综合下来,还是看使用的业务场景选择不同的隔离级别,个人感觉大部分业务还是用rc比较好。你觉得呢? 附sql脚本 console1.sql
use test;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#READ UNCOMMITTED(未提交读)
start transaction ;
select * from t where id =1;
update t set point=50 where id =1;
commit ;
#READ COMMITTED(读已提交)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
select * from t where id =1;
start transaction ;
update t set point=80 where id =1;
insert into t values (null,200);
select * from t where id =2;
commit ;
#REPEATABLE READ(可重复读)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ ;
select * from t ;
start transaction ;
update t set point=100 where id=1;
commit ;
start transaction ;
select * from t ;
insert into t values (null,300);
select * from t ;
commit
## SERIALIZABLE(可串行化)
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE ;
start transaction ;
select * from t;
insert into t values (null,123);
console2.sql
use test;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#READ UNCOMMITTED(未提交读)
start transaction ;
select * from t where id =1;
select * from t where id =1;
commit ;
#READ COMMITTED(读已提交)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
select * from t where id =1;
start transaction ;
update t set point=10 where id =1;
select * from t where id =1;
select * from t where id =2;
commit ;
#REPEATABLE READ(可重复读)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ ;
select * from t ;
start transaction ;
select * from t where id=1;
select * from t where id=1;
start transaction ;
select * from t ;
select * from t ;
insert into t values (3,300);
commit
## SERIALIZABLE(可串行化)
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE ;
start transaction ;
select * from t;
commit;
本篇文章由一文多发平台ArtiPub自动发布