事务的四大隔离级别的实验
数据库实验之事务的四大级别操作
背景介绍:
一、事务的四大特性(ACID)
了解事务隔离级别之前不得不了解的事务的四大特性。
1、原子性(Atomicity)
事务开始后所有操作,要么全部做完,要么全部不做。事务是一个不可分割的整体。事务在执行过程中出错,会回滚到事务开始之前的状态,以此来保证事务的完整性。类似于原子在物理上的解释:指化学反应不可再分的基本微粒,原子在化学反应中不可分割 。
2、一致性(Consistency)
事务在开始和结束后,能保证数据库完整性约束的正确性即数据的完整性。比如经典的转账案例,A向B转账,我们必须保证A扣了钱,B一定能收到钱。个人理解类似于物理上的能量守恒。
3、隔离性(Isolation)
事务之间的完全隔离。比如A向一张银行卡转账,避免在同一时间过多的操作导致账户金额的缺损,所以在A转入结束之前是不允许其他针对此卡的操作的。
4、持久性(Durability)
事务的对数据的影响是永久性的。通俗的解释为事务完成后,对数据的操作都要进行落盘(持久化)。事务一旦完成就是不可逆的,在数据库的操作上表现为事务一旦完成就是无法回滚的。
在现代互联网中,为了满足大量用户同时操作数据的需求,类似于双十一淘宝,现代的数据库普遍都有着并发机制,但是任何事物得到一样性能必须会牺牲另一项性能,为了实现并发性,也往往造就了许多的问题,这一点无论在数据库,亦或是操作系统中都有体现。
Q: 发生的问题:
1、脏读
又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读。
2:可重复读
同一个事务中,多次读出的同一数据是不一致的。
3:脏读是指当事务不是独立执行时发生的一种现象。
事务A读取与搜索条件相匹配的若干行。事务B以插入或删除行等方式来修改事务A的结果集,然后再提交。
A:如何解决这个问题呢?
为此mysql 的innodb 引擎中,引入了不同的事务隔离机制,用来解决这些问题。
进行实验:
1:建表:
CREATE TABLE accout(
id INT NOT NULL,
personname VARCHAR (255) NOT NULL,
balance INT NOT NULL,
PRIMARY KEY (id)
)
2: 插入数据:
INSERT INTO accout
VALUES(1,"alice" ,1000),
(2, "bob", 900),
(3, "jack", 800),
(4, "Amy" , 700),
(5,"Andy", 600)
此时的表:
set session transaction isolation level read ;
START TRANSACTION;
UPDATE accout SET balance = balance + 100;
设置隔离等级读未提交
set session transaction isolation level read uncommitted;
开启两个事务: session 1 和session 2
其中 : session 2 :
set session transaction isolation level read ;
START TRANSACTION;
UPDATE accout SET balance = balance + 100;
执行 ,同时 在session 1 中查询结果为:
select * from accout;
结果:
由于某种原因 session 2 回滚,同时 session1 进行余额减100 的操作
ROLLBACK;
COMMIT;
UPDATE accout set balance = balance -100;
commit;
selet * from accout;
结果:
我们会发现,莫名其妙的多了100 元, 为啥 ?
这是因为,在 session1开始加钱100 元时, session1觉得查询结果就是正确的数据。
我们发现因为session1的脏读造成了最终数据不一致。正确的结果应该为Andy 500;
到此我们怎么避免脏读呢,将事务的隔离性增加一个级别到read-commit
read - commit 解决脏读
设置 事务隔离级别 ,
set session transaction isolation level read committed;
重复上述操作:
session 2
session1 中查询到的数据:
我们发现将 事务的隔离升级为read-committed;后有效的隔离了两个事务,使得session1中的事务无法查询到session2中事务对数据的改动。有效的避免了脏读
接着操作:
结果符合
read-commit的不可重复读
重置数据,使数据恢复到初始状态
开启事务:
start transaction;
session1 首次查询:
session 2 进行更新,
set session transaction isolation level read committed;
START TRANSACTION;
UPDATE accout SET balance = balance - 100;
SELECT * FROM accout;
session 1 再次查询,发现一致,原因是session2没有commit,
但是当commit 后,再次查询结果如下
查看查询结果可知,session1在开启事务期间发生重复读结果不一致
产生了不可重复读。
repeatable-read可重复读
恢复数据 , 设置隔离级别为 repeatable-read
set session transaction isolation level repeatable read;
重复上述操作 ,session1 的第二次查询为
看起来并未改变 , 让我们探索一下这是如何保证两个这两个事务的互不影响。
我们在session1 中也进行
UPDATE accout SET balance = balance +100;
发现无法进行,原来是锁机制,只有session2 commit后 ,session1 才能运行
-repeatable-read的幻读
1 :两事务都运行:
set session transaction isolation level repeatable read;
start transaction;
select * from accout;
产生快照
2 :session 2
INSERT INTO accout values(6,"wen" , 50);
commit;
3:::在session 1 中 进行查询,发现结果为
此时按照我们通常的业务逻辑,此时应该是能成功插入id=6的数据
INSERT INTO accout values(6,"wen" , 50);
,结果插入失败,提示该条已经存在,但是我们查询里面并没有这一条数据啊。为什么会插入失败呢?
因为①中的select语句生成了快照,之后的读操作(未加读锁)都是进行的快照读,即在当前事务结束前,所有的读操作的结果都是第一次快照读产生的快照版本。疑问又来了,为什么②步骤中的select语句读到的不是快照版本呢?因为update语句会更新当前事务的快照版本
那么该如何解决呢?
采用当前读的方式
select * from tb_bank lock in share mode;//采用当前读
这样就解决了快照问题。
可串行化:
使用了SERIALIZABLE——可串行化隔离级别时,在这个事务没有被提交之前
其他的线程,只能等到当前操作完成之后,才能进行操作,这样会非常耗时,而且,影响数据库的性能,通常情况下,不会使用这种隔离级别
牺牲了性能 ,是的线程只能被当前执行完才能进行下一个问题。