事务
- 一、概念和特性
- 二、事务是如何实现的?
-
- [<一>、原子性(undo log)](#<一>、原子性(undo log))
- [<二>、持久性(redo log)](#<二>、持久性(redo log))
- [<三>、隔离性(mvcc + lock)](#<三>、隔离性(mvcc + lock))
- <四>、一致性(constraint)
一、概念和特性
1、何为事务
- 事务是指逻辑上的一组操作,组成这组操作的各个单元要么全部成功,要么全都失败
2、事务的四大特性(ACID)
- 原子性(Atomicity):事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
- 一致性(Consistency):事务执行的结果必须使数据库从一个一致性状态到另一个一致性状态
- 隔离性(Isolation):多个并发用户访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离,隔离性由隔离级别保障
- 持久性(Durability):一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
3、事务并发问题
- 脏读:一个事务读到了另一个事务未提交的数据
- 不可重复读:一个事务读到了另一个事务已经提交的数据。引发事务中的多次查询结果不一致
- 幻读:一个事务读到了另一个事务已经插入的数据。导致事务中多次查询的结果不一致
4、事务隔离级别
- read uncommitted 读未提交:一个事务读到另一个事务没有提交的数据。产生问题:脏读、不可重复读、幻读
- read committed 读已提交:一个事务读到另一个事务已经提交的数据。产生问题:不可重复度、幻读
- repeatable read 可重复读:在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
- serializable 串行化:同时只能执行一个事务。
二、事务是如何实现的?
<一>、原子性(undo log)
- 原子性的实现主要是靠数据库中的回滚机制,回滚则是通过undo log实现的,所以undo log也称为回滚日志
- undo log能够保证事务在回滚时,能够撤销所有执行成功的Sql
- undo log是逻辑日志,记录的是sql相关的信息,比如在执行insert时,undo log记录的是delete语句;反之执行delete语句的时候,undo log保留的是insert
<二>、持久性(redo log)
1、redo log
- Mysql最终的数据都是存在磁盘中的,但对mysql的操作如果都是读写磁盘的话,就太慢了,所以mysql提供了buffer pool,buffer pool包含了磁盘中部分数据页的映射
- 从mysql读取数据时,会先从buffer pool读取数据,如果没有,则从磁盘中读到buffer pool中
- 当向mysql写入数据时,会先写到buffer pool中,buffer pool更新的数据会定期刷新到磁盘中(此过程称为刷脏),如果在刷脏的过程中,mysql突然出现宕机,就会导致出现数据丢失
- redo log就是解决这个问题,采用预写的方式记录日志,先记录日志,再刷数据到buffer pool,这样在mysql出现宕机时,可以从磁盘中读取redo log进行数据的恢复,从而保证了数据的持久性
- redo log最主要的作用就是用于数据库异常宕机的恢复工作,假如数据库永远不会发生异常宕机,那么根本不需要redo log,因为InnoDB有线程不断的把做把脏页刷新到磁盘的工作,数据库如果一直不宕机,就不会有问题
2、binlog
与redo log常用来比较的是binlog,binlog主要是用来做备份的,下面是它们的一些区别
- redo log是innoDB存储引擎特有的;binlog是server层,属于共有的
- redo log是物理日志,记录某个数据页的修改;binlog是逻辑日志(记录DML语句)
- redo log是一个环,循环写;binlog是追加写,不会覆盖以前的日志
- redo log用于异常重启;binlog用于备份
- binlog以事件的形式记录了所有的DML和DDL语句(因为它记录的是操作而不是数据值,属于逻辑日志),可以用来做主从复制和数据恢复
<三>、隔离性(mvcc + lock)
- 原子性和持久性都是基于单个事务内部的措施,而隔离性是多个事务相互隔离、互不影响的特性
- 事务间的写操作主要是通过锁来实现的,但如果读也加锁的话就会极大地降低并发所以产生了MVCC机制
1、MVCC
- 全称:MutilVersion concurrency control,多版本并发控制,核心思想是读不加锁,读写不冲突
- MVCC实现原理是数据快照,不同事务访问不同版本的数据快照。
首先不同版本的数据快照是由undo log实现的
- Innodb下的表有默认字段和可见字段,默认字段有隐藏的列,默认字段最关键的两个列,一个保存了行的事务id ,一个保存了行的回滚指针,如下所示
sql
insert into user (id,name,age,sex) values (1,'李世民','2000','男');
trx_id | roll_pointer | id | name | age | sex | |
---|---|---|---|---|---|---|
rowid | 1 | NULL | 1 | 李世民 | 2000 | 男 |
sql
执行update语句
update user set name = '武则天',sex = '女' where id = 1;
- --最新版本
trx_id | roll_pointer | id | name | age | sex | |
---|---|---|---|---|---|---|
rowid | 2 | aaaa | 1 | 武则天 | 2000 | 女 |
- --上个版本,上面那个版本的回滚指针就是指的下面这条的地址
trx_id | roll_pointer | id | name | age | sex | |
---|---|---|---|---|---|---|
rowid | 1 | NULL | 1 | 李世民 | 2000 | 男 |
其次,当前事务能看到哪个版本的数据快照是由readview实现的
- 对于读未提交隔离级别来说,直接读取最新版本记录就好了,不需要读取快照版本
- 对于串行化隔离级别来说,读写都加锁,也不需要快照版本
- 对于读已提交和可重复读隔离级别来说,就需要undo log中的版本链
2、MVCC中的读操作
- MVCC中的读不加锁是相对的,快照读不会加锁,同时还会根据隔离级别来判断会不会加锁,串行化读写都会加锁
快照读:普通的select操作
sql
select * from table where ?
快照读就是通过MVCC实现的,访问可见版本的数据
当前读:特殊的读操作,插入/更新/删除操作,需要加锁
sql
update table set ? where ?;
insert into table values ();
delete table where ?;
select * from table where ? lock in share mode;
select * from table where ? for update;
为什么当前读需要加锁?
数据库中的insert/update/delete操作都是伴随着读取数据的,且必须是当前最新数据,所以称为当前读
当一条update语句发送给Mysql后,Mysql server会根据where条件,读取第一条满足条件的记录,然后innodb引擎调用读取数据接口,返回这条记录,并加锁,会再发起一条update请求,更新这条记录,因此update操作内部包含了一个当前读,同理,delete操作也是如此,insert稍微有些不同,简单来说,insert的时候会触发unique key的冲突检查,也会进行一个当前读
<四>、一致性(constraint)
- 一致性主要是指事务前后数据的完整性没有被破坏
- 可以通过主键、外键、唯一约束、非空约束等来完成,如果有事务破坏这些约束,就不可以进行提交
- 此外原子性、持久性、一致性都是为了保证最终的一致性