数据库的四大特性ACID
为了解决脏读,幻读,不可重复读的问题,数据库为我们提供了四种隔离级别
事务的隔离性使用的是MVCC和锁实现的,那么什么是MVCC呢?
数据库的并发有三种情景
MVCC解决的问题
MVCC是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决以下问题:
三个隐藏字段
undo log
在进行insert,delete,update操作的时候产生的方便回滚的日志。
当进行insert操作的时候,产生的undolog只在事务回滚的时候需要,并且在事务提交之后可以被立刻丢弃
当进行update和delete操作的时候,产生的undolog不仅仅在事务回滚的时候需要,在快照读的时候也需要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
当一个事务要对一条数据进行修改的时候会对这行数据加排他锁,将当前记录拷贝到undolog中,拷贝完毕后,进行对应修改,并将隐藏字段中的事务ID改为新事务ID,回滚指针指向之前拷贝的副本记录。
read view
事务进行快照读操作的时候生成的读视图,在改事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃的事务id,事务的id值是递增的。主要作用是用来进行可见性分析
目的:之前做的修改能否对读有影响
过程:将要被修改的数据的最新记录中的DB_TRX_ID取出来,与系统当前其他活跃事务ID进行比较,如果DB_TRX_ID跟read view的属性做了比较不符合可见性,那么就通过DB_ROLL_PTR回滚指针去去undolog中的DB_TRX_ID进行遍历操作直到找到符合条件的版本。
重要属性:trx_list(read view生成时刻所有正在活跃的事务ID放到这个集合中),up_limit_id(记录trx_list中事务ID最小的ID),low_limit_id(read view 生成时刻下一个暂未分配的事务ID)
规则:1、首先比较DB_TRX_ID < up_limit_id,如果小于,则当前事务能看到DB_TRX_ID所在的记录,如果大于等于进入下一个判断,2、接下来判断DB_TRX_ID >= low_limit_id,如果大于等于则代表DB_TRX_ID所在的记录在Read View生成后才出现的,那么对于当前事务肯定不可见,如果小于,则进入下一步判断, 3、判断DB_TRX_ID是否在活跃事务中,如果在,则代表在Read View生成时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明这个事务在Read View生成之前就已经开始commit,那么修改的结果是能够看见的。
概述图及流程图如下
原理
建表如下
create table t1
(
id int not null
primary key,
name varchar(20) null,
age int null
);
插入数据
INSERT INTO test_db.t1 (id, name, age) VALUES (1, '1', 1);
INSERT INTO test_db.t1 (id, name, age) VALUES (2, '2', 2);
事务1执行
SELECT @@transaction_isolation;
###################RR隔离级别下read view生成时机##########################
# 1.关闭自动事务提交
set autocommit = 0;
# 2.开启事务
begin ;
# 4.查询表数据
select * from t1;
# 8.查询表数据
select * from t1;
# 10.再次查询表数据(查不到说明read view是在事务开始的时候生成的)
select * from t1;
# 11.提交事务
commit ;
###################RC隔离级别下read view生成时机##########################
# 1.设置隔离级别为RC
set session transaction isolation level read committed ;
# 2.开启事务
begin ;
# 6.查询表数据
select * from t1;
# 7.在此查询表数据(可以查到数据)
select * from t1;
# 8.提交
commit ;
事务2执行
###################RR隔离级别下read view生成时机##########################
# 3.开启事务
begin ;
# 5.查询表数据
select * from t1;
# 6.新增一条数据
insert into t1 values (3,'3',3);
# 7.在此查询表数据
select * from t1;
# 9.提交事务
commit;
###################RC隔离级别下read view生成时机##########################
# 3.开启事务
begin ;
# 4.新增数据
insert into t1 values (4,'4',4);
# 5.查询表数据
select * from t1;
# 6.提交事务
commit;
建表
-- auto-generated definition
create table user
(
id int auto_increment
primary key,
name varchar(255) null,
age int null
);
事务1执行
###################幻读产生##########################
truncate table user;
# 0.数据准备
INSERT into user VALUES (1,'1',20),(5,'5',20),(15,'15',30),(20,'20',30);
# 1.开启事务
begin ;
# 3.查看数据
select * from user;
# 7.修改数据
update user set name = '9' where age = 20;
# 8.在此查询数据获取到了最新的数据
select * from user;
# 提交事务
commit ;
###################解决幻读##########################
# 1.设置锁的输出信息为on
set global innodb_status_output_locks = on;
# 2.开启事务
begin ;
# 5.查看锁信息
show engine innodb status;
# 6.加锁操作
select * from user where age = 20 for update ;
commit ;
事务2执行
###################幻读产生##########################
# 2.开启事务
begin ;
# 4.查看数据
select * from user;
# 5.新增数据
insert into user values (25,'25',20);
# 6.提交事务
commit ;
###################解决幻读##########################
# 3.开启事务
begin ;
# 4.查询数据
select * from user where age = 20;
# 7.新增数据会阻塞
insert into user values (30,'30',20);
# 8.提交事务
commit ;