本文章主要解释以下主要几个问题:
事物与ACID属性:
- 原子性(Atomicity)
- 一致性(Consistent)
- 隔离性(Isolation)
- 持久性(Durable)
并发事物处理带来的问题
- 更新丢失
- 脏读
- 不可复读
- 幻读
事物与ACID属性:
1、原子性 Atomicity
事务的原子性指的是,事务中包含的程序作为数据库的逻辑工作单位,它所做的对数据修改操作要么全部执行,要么完全不执行。这种特性称为原子性。
2、一致性 Consistency
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。这种特性称为事务的一致性。假如数据库的状态满足所有的完整性约束,就说该数据库是一致的。
3、隔离性 Isolation
隔离性指并发的事务是相互隔离的。即一个事务内部的操作及正在操作的数据必须封锁起来,不被其它企图进行修改的事务看到。
4、持久性 Durability
持久性意味着当系统或介质发生故障时,确保已提交事务的更新不能丢失。即一旦一个事务提交,DBMS保证它对数据库中数据的改变应该是永久性的,耐得住任何数据库系统故障。持久性通过数据库备份和恢复来保证。
这里不对ACID做过多的解释,大学课本里面有的
数据库表的创建及数据插入见文末
行锁重现
这里分为事物A与事物B,模拟多线程操作环境
事物A
begin
--开启一个事物Aupdate account set balance =3000 where id=3;
-- 更新id=3 余额此时事物中Seesion中id=3余额已经修改为3000
事物A挂起,事物B进入
此时事物B提交等待,应为事物A对id=3的行进行操作并未提交,过一段时间后会显示超时
这段时间如果事物A提交,事物B也会随即提交
并发事物处理带来的问题:
- 更新丢失: 两个事物同事操作相同的数据,后提交的事物会先覆盖先提交的事物处理结果通过乐观锁就可以解决
- 脏读: 事物A读取到了事物B以及修改单尚未提交的数据,如果事物B回滚,A读取的数据无效,不符合一致性
- 不可复读: 事物A读取到了事物B已经提交的修改数据,不符合隔离性
- 幻读 : 事物A读取带了事物B提交的新增数据,不符合隔离性
更新丢失和脏读是非常容易理解的,这里解释一下不可复读,
不可复读:事物A先从数据库查询到变量a=2,此时事物B修改a=3并且提交,然后事物A查询变量a的值应该为多少呢?
不可复读指的就是事物A读到a=3.开始事物A操作都是基于a=2,
事物B修改后a=3;此时肯定会出错,事物A操作都是基于账号2,但是后面却事物B介入。
这样就代码没办法去写以一定漏洞百出
幻读:与不可复读类似,事物A读数据库中只有一条数据,事物B将这条记录删除,此时事物A再去读表中将不会有记录。这样数据库逻辑不符合,无法进行
四种事物隔离级别
下面进行实列验证
- Mysql默认事物隔离级别是REPRATABLE-READ(可从重复读)
数据库事物常用命令
--开启事务
begin;
--显示数据库事物隔离级别
show variables like 'tx_isolation';
--设置数据库事物隔离级别
set tx_isolation ='REPRATABLE-READ';
--提交事物
commit;
--回滚
roolback;
- 1 、脏读
蓝色框执行顺序无要求,黄色框按照指定顺序执行
对应步骤的执行结果下图:
- 2、不可复读
蓝色框执行顺序无要求,黄色框按照指定顺序执行
对应步骤的执行结果下图:
在此逻辑情况下肯定也会出现问题,假设一个线程A先读取到balance的值400,根据自己逻辑情况下Java将该值设为100;那么此时数据逻辑该怎么进行的!操作都是基于400,一段时间后却变为了350.
- 3、不可复读
蓝色框执行顺序无要求,黄色框按照指定顺序执行
Mysql数据库默认的隔离级别就是“可重复读”,所以此时为进行set
对应步骤的执行结果下图:
幻读现象肯定也是不符合业务逻辑的,比如事物A读取到账户1,并且基于账户A操作,此时另外一个线程介入开启另外一个事物并删除了1(或者新增了账户),此时另外事物A基于账户1操作,半途却发现一个账户都没有咯。 - 4 、可串行化就是前面几种情况都不会出现,相当于加入一把大锁,业务进入即将表锁定。但是效率极低,所以数据库一般采用的可重复读级别
MVCC机制:此时同一个事物在执行第一次的Select时候回建立一个快照,在此查询是会读取到快照的。锁幻读情况的重现会出现问题,先执行一次update事物B新增的,再次进行select时候就能出现幻读
表创建sql
CREATE TABLE account(
id int(11) NOT NULL AUTO_INCREMENT,
name VARCHAR (255) DEFAULT NULL,
balabce INT(11) DEFAULT NULL,
PRIMARY KEY(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT into account(name,balance)VALUES("李雷","450");
INSERT into account(name,balance)VALUES("韩梅梅","16000");
INSERT into account(name,balance)VALUES("lucy","2400");