文章目录
- [什么是 事物](#什么是 事物)
- 为什么出现了事物
- 事物的版本支持
- 事物的四大属性
- 事物的提交方式
- 事物常见操作方式
- 事物的隔离性
-
- 查看与设置隔离性
- [读未提交【read uncommitted】](#读未提交【read uncommitted】)
- [读提交【read committed】](#读提交【read committed】)
- [可重复读【repeatedable read】](#可重复读【repeatedable read】)
- 串行化【serializable】
- 数据库并发访问的三种场景:
- MVCC机制(读写并发)
什么是 事物
事物就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,他们是一个整体。Mysql提供一种机制,保证我们达到这样的效果,事物还规定不同的客户端看到的数据是不同的。
例如,当你在大学毕业时,学校的数据库可能不需要你的数据了,所以要删除你的信息,即要删除所有你的信息,你的各科成绩等等,这些所有的语句合起来就是一个事务。
为什么出现了事物
事物被MySQL设计出来,本质是为了当应用程序访问数据库的时候事物能够简化我们的编程模型,不需要我们考虑各种各样的潜在错误和并发问题,因为我们使用事物的时候,要么提交,要么回滚,我们不用再考虑网络异常、服务器宕机、并发访问等问题,所以事物就是为了服务应用层而诞生的。
事物的版本支持
在MySQL中,只有使用innodb存储引擎的才支持事物,Myisam不支持事物。
事物的四大属性
- 原子性:
- 一致性: 在事物开始之前和之后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有预设规则,包含资料的准确度、串联性以及后续数据库可以自发性地完成预定工作。
- 隔离性:
- 持久性:
原子性+隔离性+持久性 共同保障了一致性,一致性也需要用户的配合。
事物的提交方式
事物提交有两种方式:手动提交和自动提交。
sql
//查看事物的提交方式:
show variables like 'autocommit';
//更改事物的提交方式
set autocommit=0;
//set autocommit=0 即禁止自动提交
事物常见操作方式
正常情况
sql
//先检测一下默认提交方式:
show variables like 'autocommit';
//启动事物
start transaction;
begin;
//设置保存点
savepoint sp_name;
//回滚到保存点
rollback sp_name;
//完全回滚
rollback;
//提交事物
commit;
事物只能在启动期间才能回滚,一旦提交,事物就无法回滚了!
非正常情况
客户端异常退出,MySQL自动完成事物的回滚。
单条SQL和事物的关系:所有的单SQL语句都是事物,如果关掉了自动提交,每一条单SQL都需要我们手动commit去提交。
当我们手动begin时,不会受autocommit的影响。
通过事物的启动和回滚,体现出Mysql的原子性和持久性。
事物的隔离性
在事物场景里,隔离是必要的。运行中的事物,进行互相隔离,根据影响程度的不同,设置隔离级别。
- 读未提交
- 读提交
- 可重复读
- 串行化
查看与设置隔离性
sql
//查看全局隔离级别
select @@global.tx_isolation; # 旧版
select @@global.transaction_isolation; # 新版
//查看当前会话的隔离级别
select @@session.transaction_isolation;
//查看当前会话的隔离界别
select @@transaction_isolation;
//设置隔离级别
set [session | global] transaction isolation level {read uncommitted | read committed | repeatable read | serializable}
读未提交【read uncommitted】
一个事务的增删改操作还没有提交,其他事务立马就能看到。
几乎没有加锁,虽然效率很高,严重不推荐使用。
脏读:
一个事务 在执行中,读到另一个执行中事务的更新(其他操作)但是未commit的时候,被称为脏读。
这里需要注意,一定是两个事务都在执行中 ,而假如一个事务已经提交,另一个执行中的事务看到了,并不是脏读。
读提交【read committed】
一个事务发生修改后,提交后,其他事务就能看到(不管自己是否commit)
不可重复读
同样的读取,在不同的时间段,读取的结果不一样,这种现象就叫做不可重复读。
注意是一个事务执行中!
不可重复读是问题吗?
当然是问题!
可重复读【repeatedable read】
只有自己的事务结束后,才能感知到其他事务发生的改变!
幻读:
insert 插入的数据在可重复读情况被读取出来导致多次查找时会多查出新的记录,如同产生了幻觉,这种情况被称为幻读。 因为insert的数据之前数据库里都没有,不能通过普通加锁来实现。
MYSQL解决了幻读问题。
注意,在RR级别下,虽然读写不加锁,但写写操作依然是要加锁的!
串行化【serializable】
将所有的MYSQL事务串行化。
数据库并发访问的三种场景:
- 读读并发:不存在问题
- 写写并发:有线程安全的问题
- 读写并发:有线程安全的问题
首先,每个事务都要有自己的事务id,根据事务的id大小,来决定事务来到的先后顺序。
其次,mysqld 可能会面临同时处理多个事务,事务存在自己的生命周期,mysqld要对多个事务进行管理,事务也就是一个或一套结构体对象/类对象。
MVCC机制(读写并发)
- 三个记录隐藏字段
- undo 日志
- Read View
四个隐藏字段:
- DB_TRX_ID:6byte, 最近修改事务ID,记录最后一次更改的事务ID。
- DB_ROLL_PTR:7byte,回滚指针,指向这条记录的上一个版本。
- DB_ROW_ID:6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,innodb会自动以DB_ROW_ID产生一个聚簇索引。
- flag隐藏字段:记录被更新或删除并不是真正的删除,而是改变了flag字段。
name | age | DB_TRX_ID | DB_ROW_ID | DB_ROLL_PTR |
---|---|---|---|---|
张三 | 28 | 9 | 1 | null |
undo日志
undo log是mysql 中的一段内存缓冲区,用来保存日志数据。
例如,当我们一个事务修改了一行记录后,会先将该行记录加锁,同时保留一份放在undo log里,完成修改后让记录的回滚指针指向undo log里的那个历史版本。
这样就形成了一个基于链表的历史回滚链。
历史版本不能更改!!!
undo log记录相反sql的方式来实现回滚的操作。
select 读取,是读取当前最新版本?还是读取历史版本?
当前读
:读取最新的记录,增删改都是当前读。
历史读(快照读)
:读取快照版本。
读写为什么能够并发:因为我们读取是读的快照版本,而写是写的最新版本,两个版本不一样!
如何保证不同的事务,看到不同的内容?如何实现隔离级别?
通过隔离级别,可以让不同事务看到不同的内容。
Read View【读视图】
read view在MySQL中是一个类,本质是用来进行可见性判断的。
当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图。把它当作条件,用来判断当前事务能够看到哪个版本的数据,既可能是最新的数据,也可能是该行记录的undo log里面某个版面的数据。
cpp
class Readview
{
private:
//大于等于这个ID的事务均不可见
trx_id_t m_low_limit_id;
//小于等于这个ID的事务均可见
trx_id_t m_up_limit_id;
//创建该 ReadView 的事务ID
trx_id_t m_creator_trx_id;
//创建视图时的活跃事务ID列表
ids_t m_ids;
trx_id_t m_low_limit_no;
//标记视图是否被关闭
bool m_closed;
//...省略
}
//
m_ids //用来维护Read View生成时刻,系统活跃的事务ID
up_limit_id //记录m_ids中事务ID最小的ID
low_limit_id //Read View生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过事务ID的最大值+1
creator_trx_id //创建Read View的事务ID
怎么判断事务能看到哪些历史版本呢?
我们在实际读取数据版本链的时候,是能够读取到每一个版本对应的事务ID的,即DB_TRX_ID;
- creator_trx_id == DB_TRX_ID || DB_TRX_ID < up_limit_id
如果事务的id等于记录的事务ID,那说明这条记录就是我修改的,应该看到。
如果记录的事务ID比当前活跃的最小的事务ID还要小,那就说明这个事务已经跑完了,应该看到
- DB_TRX_ID >= low_limit_id
记录的事务ID 大于 我认为的最大的事务ID,说明是快照形成之后才提交的,不应该看到
- 如果DB_TRX_ID 不在m_ids里,说明已经提交,可以看到,如果在,说明这个事务也是活跃事务,不可以看到。
readview是事务可见性的一个类,不是事务创建的,而是事务已经存在后,首次进行快照读的时候,才会形成read view。
实验:
可以看出,快照形成的时机不同,结果也会不同。
RR 和 RC的本质区别:
RC和RR正是Read View生成时机不同,从而造成快照读的结果不同。
RR在调用多次快照读的时候,都是使用的第一次快照读时产生的Read View。
所以每次读的数据都是一样的
RC在调用多次快照读时,每一次都会生成一个快照和Read View
所以会出现不可重复读的问题。