什么是事务?

事务的概念

数据库事务是构成单一逻辑工作单元的操作集合

事务的特性

原子性(Atomicity) 原子性是指事务是一个不可分割的工作单元,事务中的操作要么全部发生,要么全部不发生

一致性(Consistency) 事务前后数据的完整性必须保持一致

隔离性(Isolation) 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离

持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

原子性的实现原理 —— Undo Log

所谓的Undo Log就是“回滚日志”,当进行插入、删除、
修改操作时,一定会生成Undo Log,并且一定优先于修改后的数据落盘

可以看到,每一条变更数据的操作,都伴随一条undo log的生成。undo log就是记录数据的原始状态。

有了undo log,如果事务执行失败要回滚,那就简单了,直接将undo log里的回滚语句执行一遍就好了。Mysql就似乎通过这种方式完成的原子性。

可以理解为:

当delete一条记录时,undo log中会记录一条对应的insert语句。
当insert一条记录时,undo log中会记录一条对应的delete语句。
当update一条语句时,undo log中会记录一条相反的update语句。

持久性实现原理 —— Redo Log

和Undo Log相反,Redo Log记录的是新数据的备份。

在事务提交前,只要将Redo Log持久化,不需要将数据持久化,当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化,系统可以根据Redo Log中的内容,将所有数据恢复到最新的状态。

举个例子:

比如你开了一家小饭馆,中午忙的时候如果有人想赊账,就没办法拿出总帐本来找出这个人以前的赊账记录,然后添加上去,所以一般都会在门口放一个小黑板,先临时记录在小黑板上,等有时间了再抄到帐本里,这个小黑板的作用就相当于Redo Log,它是可以循环写入的。

Undo Log记录某 数据 被修改 的值,可以用来在事务失败时进行rollback;
Redo Log记录某 数据块 被就修改 的值,可以用来恢复未写入data file的已成功事务更新的数据。

即,

  • Redo Log保证事务的持久性

  • Undo Log保证事务的原子性

 

bin log和redo log的区别:
https://www.jianshu.com/p/907f9002442e

 

Log落盘过程

重要参数:

innodb_flush_log_at_trx_commit:用来控制redo log刷新到磁盘的策略

当设置为1的时候,事务每次提交都会将log buffer中的日志写入os buffer并调用fsync()刷到log file on disk中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。

当设置为0的时候,事务提交时不会将log buffer中日志写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中。mysqld进程的崩溃会导致上一秒钟所有事务数据的丢失。

当设置为2的时候,每次提交都仅写入到os buffer,然后是每秒调用fsync()将os buffer中的日志写入到log file on disk。只有在操作系统崩溃或者系统断电的情况下,上一秒所有事务数据才可能丢失。

总结:

0(延迟写,实时刷):log_buff —每隔1秒—> log_file ——实时——> disk

1(实时写,实时刷):log_buff ——实时——> log_file ——实时——> disk

2(实时写,延迟刷):log_buff ——实时——> log_file –每隔1秒–> disk

 

0:最快减少mysql写的等待

1:最大安全性,不会丢失数据

2:折中,减少操作系统文件写入等待时间

 

隔离性实现原理 —— 锁

在Mysql中,锁可以分为两类:

   共享锁(读锁):获得共享锁之后,可以查看但无法修改和删除数据。

   排他锁(写锁):又称为独占锁。获得排他锁后,既能读数据,又能修改数据。

举个例子:

一般情况下,我们进入洗手间有可能做以下几件事:洗手,化妆,上厕所等。其实只有上厕所这件事是极度隐私的,而其他几件事并没有那么隐私。
我们可以认为洗手间就是一个数据库表,而洗手间内部的设施就是数据库表中的数据。
简单的洗手、化妆等操作可以认为是读操作,而上厕所可以认为是写操作。

共享锁

有些时候,如果们进入洗手间只是想洗手的话,我们一般不会锁门。而其他人也可以进来洗手、化妆等。但是,其他人是不可以进来上厕所的。

我们对数据进行读取操作的时候,其实是不会改变数据的值的,所以我们可以给数据库增加读锁,获得读锁的事务就可以读取数据了。当数据库已经被别人增加了读锁的时候,其他新来的事务也可以读数据,但是不能写。

也就是说,如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

用法

SELECE ... LOCK IN SHARE MODE;

排他锁

如果我们进入洗手间只是想洗手,那么我们可以允许其他人也进来洗手。但是,如果我们进入洗手间是为了上厕所,那么任何人不能再进来做任何事。

这就是排他锁,也叫写锁。我们对数据进行写操作的时候,要先获得写锁,获得写锁的事务既可以写数据也可以读数据。如果数据库已经被别人增加了排他锁,那么后面的事务是无法再获得该数据库的任何锁的。

也就是说,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任何类型的锁。获准排他锁的事务既能读数据,又能修改数据。

用法

SELECT ... FOR UPDATE;

当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。

Mysql隔离级别

事务具有隔离性,理论上来说,事务之间的执行不应该相互产生影响,其对数据库的影响应该和它们串行执行时一样,然而,完全的隔离性会导致系统并发性能很低,降低对资源的利用率,因而实际上对隔离性的要求会有所放宽,这也会一定程度上造成对数据库一致性要求降低。

Mysql为事务定义了不同的隔离级别,从低到高依次时:

读未提交(对事务处理的读取没有任何限制,不推荐)
读已提交
可重复读(Mysql默认)
串行化(效率低,不推荐)

设置隔离级别:

set global transaction_isolation=’read-committed’;

不同的隔离级别可能导致不同的并发异常

脏读即为事务1第二次读取时,读到了事务2未提交的数据。若事务2回滚,则事务1第二次读取时,读到了脏数据。

不可重复度脏读逻辑类似。主要在于事务2在事务1第二次读取时,提交了数据,导致事务1前后两次读取的数据不一致。

幻读即事务1在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务2执行了新增数据的操作并提交后,这个时候事务1读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故多了几条数据,成为幻读。

参考文档: https://blog.csdn.net/qq_33591903/article/details/81672260

不同隔离级别的处理方式:

读未提交: 任何操作都不会加锁

读已提交:

在读已提交级别中,读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行,会对该写锁一直保持直到事务提交。

我们来分析脏读,事务2更新id=1的数据后,在提交前会对该对象写锁,所以事务1读取id=1的数据时,会一直等待事务2结束,处于阻塞状态,避免了产生脏读。

同样,我们来分析不可重复读,事务1读取id=1的数据后并没有锁住该数据,所以事务2能对这条数据进行更新,事务2对id=1的数据更新并提交后,该数据立即生效,所以事务1再次执行同样的查询,查询到的结果便与第一次查到的不同,所以读已提交防止不了不可重复读。

可重复读

实现原理(MVCC[多版本并发控制])

InnoDB在每行记录后面保存两个隐藏的列,分别保存了这个行的创建时间和行的删除时间。这里存储的并不是实际的时间值,而是系统版本号,当数据被修改时,版本号+1。

在读取事务开始时,系统会给当前读事务一个版本号,事务会读取版本号<=当前版本号的数据,此时如果其他写事务修改了这条数据,那么这条数据的版本号+1,从而比当前读事务的版本号高,读事务自然就读不到更新后的数据了。

串行化

读写数据都会锁住整张表

一致性实现原理

  • 保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证
  • 数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等
  • 应用层面进行保障,如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致。