MySql--事务

摘要

事务及其ACID属性

事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。

  • 原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。

  • 一致性(Consistent) :指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。事务内查询的数据在当前事务内必须保持一致而不会受到其它事务对数据修改的影响。

  • 隔离性(Isolation) :数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。

  • 持久性(Durable) :事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

提示
总的来说,MySQL中事务的原子性是通过undo log来实现的,事务的持久性性是通过redo log来实现的,事务的隔离性是通过读写锁+MVCC来实现的。事务的一致性通过原子性、隔离性、持久性来保证。

也就是说 ACID 四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现 AID 三大特性,才有可能实现一致性。

同时一致性也需要应用程序的支持,应用程序在事务里故意写出违反约束的代码,一致性还是无法保证的,例如,转账代码里从 A 账户扣钱而不给 B 账户加钱,那一致性还是无法保证。

在事务的具体实现机制上,MySQL采用的是WAL(Write-ahead logging,预写式日志)机制来实现的。这也是是当今的主流方案。

在使用 WAL 的系统中,所有的修改都先被写入到日志中,然后再被应用到系统中。通常包含 redo 和 undo 两部分信息。关于redo日志 和 undo日志,下文有介绍。

  • 开启只读事务:START TRANSACTION READ ONLY

    • 在只读事务中不可以对普通的表(其他事务也能访问到的表)进行增、删、改操作,但可以对用户临时表做增、删、改操作。
    • 对于只读事务来说,只有在它第一次对某个用户创建的临时表CREATE TEMPORARY TABLE执行增、删、改操作时才会为这个事务分配一个事务id,否则的话是不分配事务id。
  • 开启读写事务:START TRANSACTION READ WRITE或者 BEGINSTART TRANSACTION

    • 在读写事务中可以对表执行增删改查操作。
    • 对于读写事务来说,只有在它第一次对某个表(包括用户创建的临时表)执行增、删、改操作时才会为这个事务分配一个事务id,否则的话也是不分配事务id的
    • 有的时候虽然我们开启了一个读写事务,但是在这个事务中全是查询语句,并没有执行增、删、改的语句,那也就意味着这个事务并不会被分配一个事务id

事务Id分配策略
服务器会在内存中维护一个全局变量,每当需要为某个事务分配一个事务id时,就会把该变量的值当作事务id分配给该事务,并且把该变量自增 1。
每当这个变量的值为 256 的倍数时,就会将该变量的值刷新到系统表空间的页号为 5 的页面中一个称之为 Max Trx ID 的属性处,这个属性占用 8 个字节的存储空间。
当系统下一次重新启动时,会将上边提到的 Max Trx ID 属性加载到内存中,将该值加上 256 之后赋值给我们前边提到的全局变量(因为在上次关机时该全局变量的值可能大于 Max Trx ID 属性值)。
这样就可以保证整个系统中分配的事务id值是一个递增的数字。先被分配id的事务得到的是较小的事务id,后被分配id的事务得到的是较大的事务id。

并发事务处理带来的问题

  • 更新丢失(Lost Update)或脏写

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题–最后的更新覆盖了由其他事务所做的更新。

  • 脏读(Dirty Reads)

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;
这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。
一句话:事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。

  • 不可重读(Non-Repeatable Reads)

一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
一句话:事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性

  • 幻读(Phantom Reads)

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
一句话:事务A读取到了事务B提交的新增数据,不符合隔离性

事务隔离级别

  • 脏读不可重复读幻读,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。

隔离级别 脏读 不可重复读 幻读
读未提交(READ-UNCOMMITTED) 可能 可能 可能
读已提交(READ-COMMITTED) 不可能 可能 可能
可重复读(REPEATABLE-READ) 不可能 不可能 可能
可串行化(SERIALIZABLE) 不可能 不可能 不可能
  • 数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。

  • 不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读"和“幻读”并不敏感,可能更关心数据并发访问的能力。

  • Mysql默认的事务隔离级别是可重复读(REPEATABLE-READ)

1
2
3
4
5
6
7
8
9
# 查看当前数据库的事务隔离级别,mysql8之前使用`tx_isolation`
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+----------------+
# 设置事务隔离级别
mysql> set transaction_isolation='READ-COMMITTED';