1、四大特性
事务是一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务是一个最小的工作单元);一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成;事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同。
1.1、原子性(Atomicity)
一个事务包含多个操作,原子性就是这些批量操作要么全部执行,要么全都不执行。实现事务原子性,就要支持回滚操作,在某个操作失败后,回滚到事务执行之前的状态。
1.1.1、例子
某账户A要转给账户B一笔钱(1000元),需要完成如下操作1(A账户减1000元)、操作2(B账户加1000元),原子性就是要么批量操作全部成功执行就提交(COMMIT)数据状态为最终状态,要么批量操作部分失败执行就回滚(ROLLBACK)数据状态到开始的时候。
1.1.2、实现原理
如果事务过程种出现了异常,需要回滚数据,这时就用到undo log保证原子性。一般来说,通常进行insert/delete/update操作时会产生一条undo log,但是update更新主键操作时会产生两条undo log(即,根据主键软删除的undo log和插入新主键记录的undo log)。
1.2、一致性(Consistency)
数据库处理前后结果应与其所抽象的客观世界中真实状况保持一致。这种一致性是一种需要管理员去定义的规则。管理员如何指定规则,数据库就严格按照这种规则去处理数据。
1.2.1、例子
某用户A要转给用户B一笔钱(1000元),需要用户A钱包少1000元钱,用户B钱包多1000元钱,同时,银行数据库中账户A减1000元,账户B加1000元。
1.2.2、实现原理
通过undo log、redo log、隔离性、算法共同实现。
1.3、隔离性(Isolation)
数据库允许多个并发事务同时对某条数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
1.3.1、并发场景
事务A | 事务B | 备注 | |
---|---|---|---|
场景1 | 读 | 读 | 不存在并发冲突 |
场景2 | 读 | 写 | 存在并发冲突。(如:脏读、幻读、不可重复读) |
场景3 | 写 | 读 | 存在并发冲突。(如:脏读、幻读、不可重复读) |
场景4 | 写 | 写 | 存在并发冲突。(如:更新丢失等) |
1.3.2、隔离级别
1.3.2.1、读未提交(Read UnCommitted)
某个事务读到了另外一个事务未提交的修改过的记录(Record)。可能出现“脏读”、“不可重复读”、“幻读”的并发冲突。
1.3.2.2、读已提交(Read Committed)
某个事务读到了另外一个事务已提交的修改过的记录(Record)。可能出现“不可重复读”、“幻读”的并发冲突。
1.3.2.3、可重复读(Repeatable Read)
不管其它事务是否已经提交记录(Record),某个事务多次读取到该记录(Record)的结果一致。可能出现“幻读”的并发冲突。MVCC在一定程度可以避免幻读,但是不能完全解决幻读。
1.3.2.4、串行化(Serializable)
串行化指并行事务执行结果和串行执行结果保证一致。
1.3.3、实现原理
隔离性是通过“当前读”和“快照读”共同实现。
1.3.3.1、当前读
当前读是一种悲观锁的读操作,它会对当前读取的数据进行加锁,所以读到的数据都是最新版本数据。主要包括以下几种操作:
select …… lock in share mode --(共享锁/共享读锁)
select …… for update --(排他锁/写锁)
update …… -- (排他锁)
insert …… --(排他锁)
delete …… --(排他锁)
1.3.3.2、快照读
快照读是一种不加锁的读操作,是InnoDB并发如此之高的核心原因之一。快照读在读写时不用加锁,不过可能会读到历史版本数据,简单的 SELECT 都属于快照读,MySQL的快照读是基于MVCC(多版本并发控制)来实现的。MVCC是基于undo log、版本链、ReadView实现。
在mysql存储的版本链数据中,除了我们显式定义的字段,mysql会隐含的帮我们定义几个字段。
- trx_id:事务id,每进行一次事务操作,就会自增1。
- roll_pointer:回滚指针,用于找到上一个版本的数据,结合undo log进行回滚。
ReadView-字段 | 备注 |
---|---|
m_ids | 还未commit的活跃事务ID |
min_trx_id | m_ids的最小事务ID |
max_trx_id | 将要分配的下一个事务ID |
creator_trx_id | 创建当前ReadView的事务ID |
当我们用SELECT读取数据时,这一时刻的数据会有很多个版本(如,图 6),但我们并不知道读取哪个版本,这时就靠ReadView数据结构来对读取版本进行限制,通过“ReadView+规则”就能够知道读取哪个版本。当事务级别为“读已提交”,那么每个SELECT快照读都会生成并获取最新的ReadView,所以每次的查询结果都不一样的。当事务级别为“可重复读”,那么只有在同一个事务的第一个SELECT快照读才会创建ReadView,之后的每次快照读都使用的同一个ReadView,所以每次的查询结果都是一样的。
1.4、持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
1.4.1、实现原理
如果事务已经提交,数据未刷入磁盘,服务器宕机,这时就用到redo log保证持久性。服务器在重启的时候,可以读取redo logo中的数据,对数据进行恢复。
2、总结
2.1、四大特性如何实现的
- 原子性:通过undo log实现。
- 持久性:通过redo log实现。
- 隔离性:通过“当前读”(核心技术:悲观锁)和“快照读”(核心技术:MVCC)共同实现。
- 一致性:通过undo log、redo log、隔离性、算法共同实现。
2.2、事务的开始时间点
一般我们会认为BEGINE/START TRANSACTION是事务开始的时间点,也就是一旦我们执行了START TRANSACTION,就认为事务已经开始了,其实这是错误的。事务开始的真正的时间点(LSN)是START TRANSACTION之后执行的第一条语句,不管是什么语句、成功与否;如果想要达到将START TRANSACTION作为事务开始的时间点,那么我们必须使用START TRANSACTION WITH CONSISTNET SNAPSHOT,它的含义是执行START TRANSACTION的同时建立本事务一致性读的SNAPSHOT,而不是等到执行第一条语句时,才开始事务,并且建立一致性读的SNAPSHOT,效果等价于START TRANSACTION之后,马上执行一条SELECT语句。
2.3、隔离级别与并发冲突关系
由于隔离级别中的“读未提交”不加锁限制,“串行化”相当于单线程执行,效率太差,所以,“读未提交”和“串行化”基本上是不需要考虑的隔离级别。
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交 | 可能会 | 可能会 | 可能会 |
读已提交 | 可能会 | 可能会 | |
可重复读 | 可能会 | ||
串行化 |
- 脏读:某一个时刻,某个事务读到了另外一个事务未提交的修改过的记录(Record)。
- 不可重复读:某两个时刻,某个事务分别读取的记录(Record)不一致。
- 幻读:某两个时刻,某个事务分别读取的数量(Count)不一致,即数量变多或者变少。
2.4、MySQL的日志
日志类别 | 说明 | 备注 |
---|---|---|
错误日志 | 错误日志记录了当MySQL启动、停止或者服务器运行过程中发生任何严重错误时的相关信息,当数据库出现任何故障导致无法正常使用时,可以首先查看此日志。 | 默认开启 |
二进制日志(binlog日志) | 记录了所有的DDL和DML语句,但不包括数据查询语句,此日志对于灾难恢复时非常重要,并且MySQL的主从复制、增量恢复,也是通过该binlog 实现的。 | 默认未开启,需要手动开启 |
查询日志 | 它记录了客户端的所有操作语句,包括增删改查所有语句。 | 默认未开启,需要手动开启,注意,高并发场景企业里普通查询日志一般是关闭的(默认也是关闭的),主要是因为IO性能问题; |
慢查询日志 | 它是用来记录查询效率较低的SQL语句的日志,慢查询日志记录所有执行时间超过参数long_query_time设置值,默认值为10s。 | 默认未开启,需要手动开启 |
审计日志 | 根据国家等保审计合规要求,数据库要开启审计功能,它主要记录用户登录,对数据库的操作管理,对数据库受到的风险行为进行告警,对攻击行为及时阻断,通过审计日志可以对用户访问数据库行为进行记录、分析和汇报,用来帮助用户事后生成合规报告、事故追根溯源,同时加强内外部数据库网络行为记录,提高数据资产安全。 | MySQL企业版有此功能,属于收费组件,此测试社会版使用第三方开源插件。 |
redo log | 重做日志,提供前滚操作,通常是物理日志,记录的是数据页的物理修改,而不是某一行或者某几行修改成怎样,它用来恢复到最后一次的提交后的物理数据页。 | undo log不是redo log的逆向过程,他们都是用来恢复的日志; |
undo log | 回滚日志,提供回滚操作,用来回滚到某个版本,undo log一般是逻辑日志,根据每行记录进行回滚。 | undo log不是redo log的逆向过程,他们都是用来恢复的日志; |