MySQL学习笔记 --事务

事务

特性

事务主要是用于处理并发和从错误状态回滚到正常状态的一个操作,对于事务,有个很经典的例子:

A 打算给 B 转 200元, 则在数据库中应该进行的操作应该是下面这样:

  1. A 账户余额-200元;
  2. B 账户余额+200元;

但是如果这两条是分别执行的话, 万一 1 和 2 之间出现了什么问题(网络波动啦,临时工写的代码出Bug了啥的),那是不是 A 就白白扣了200元,而这多扣的 200 就离奇失踪了?在这种情况下,就需要事务了.

事务可以把这两个操作打包执行,也就是一荣俱荣,一损俱损.如果给 B 账户余额+200失败,则A账户余额-200也就默认失败.换句话说,事务是一个整体,而这也就是事务的原子性(Atomicity)

当然事务肯定不止这一个特性,实际上,事务特性共有4个,分别是原子性,一致性,隔离性,持久性,一般都把这四个特性简称为ACID.这些特性描述如下:

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

使用事务

SQL国际标准使用START TRANSACTION(或者是BEGIN)开始一个事务。COMMIT语句使事务成功完成。ROLLBACK语句结束事务(回滚),放弃从START TRANSACTION开始的一切变更。若autocommit被START TRANSACTION的使用禁止,在事务结束时autocommit会重新激活。

当然还有一些其他的,比如可以设置标记节点:

  • SAVEPOINT identifier,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;
  • RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
  • ROLLBACK TO identifier 把事务回滚到标记点;
1
2
3
4
5
6
7
8
9
10
--被BEGIN TRANSACTION 和 COMMIT 包裹的 SQL语句 具有ACID特性 
BEGIN;
CREATE TABLE IF NOT EXISTS `test1`(
id int,
num int
);

INSERT INTO `test1` VALUES(1,2);
SELECT * FROM `test1`;
COMMIT;

隔离级别

在讲事务的隔离级别之前,我们需要先了解一下不考虑事务的隔离性可能会出现的异常

  • 脏读(Dirty Read):当一个事务读取另一个事务尚未提交的修改时,产生脏读。
  • 不可重复读(Nonrepeatable Read):一个事务对同一行数据重复读取两次,但是却得到了不同的结果(在查询间隔中查询内容被另一个事务修改并提交了)
  • 幻读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。
  • 丢失修改(Lost Update):
    1. 当两个事务更新相同的数据源,如果第一个事务被提交,第二个却被撤销,那么连同第一个事务做的更新也被撤销
    2. 有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。

而为了兼顾并发效率异常控制,在SQL标准规范中,提供了四种隔离级别.分别是序列化,可重复读,读已提交,读未提交,详情如下:

  • Serializable (序列化):可避免脏读、不可重复读、幻读的发生。
  • Repeatable read (可重复读):可避免脏读、不可重复读的发生。
  • Read committed (读已提交):可避免脏读的发生。
  • Read uncommitted (读未提交):最低级别,任何情况都无法保证。

换成表格如下(Y表示能避免,N表示不能避免):

隔离级别名级别号脏读不可重复读幻读备注
Read uncommitted(读未提交)0NNN
Read committed (读已提交)1YNN大多数数据库默认的隔离级别
Repeatable read(可重复读)2YYNInnoDB默认级别
Serializable(序列化)3YYY

以上就是级别详情,需要注意的是:隔离级别越高,效率越低.
在 MySQL 中,可以使用select @@tx_isolation;查询当前事务的隔离级别,可以用

1
2
set  [glogal | session]  transaction isolation level Read uncommitted/Read committed/Repeatable read/Serializable;
set transaction_isolation='read-uncommitted/read-committed/repeatable-read/rerializable';

开启事务前设置事务的四种隔离级别.

事务隔离的实现

事务隔离可以借助于实现. 这里只是大致思路,MySQL具体实现参照[MySQL 5.7 Reference Manual]

  • Read Uncommited
    • 事务在读数据时候未对数据加锁
    • 事务在修改数据的时候只对数据增加行级共享锁
  • Read Committed
    • 事务对当前被读取的数据加行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁
    • 事务在更新某数据的瞬间,必须对其加行级排他锁,直到事务结束才释放
  • Repeatable Read
    • 事务在读取某数据的瞬间,必须先对其加行级共享锁,直到事务结束才释放
    • 事务在更新某数据的时候,必须先对其加行级排他锁,直到事务结束才释放
  • Serializable
    • 事务在读取数据时,必须先对其加表级共享锁,直到事务结束才释放
    • 事务在更新数据时,必须先对其加表级排他锁,直到事务结束才释放
觉得文章不错的话可以请我喝一杯茶哟~
  • 本文作者: bestsort
  • 本文链接: https://bestsort.cn/2019/08/06/86/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-SA 许可协议。转载请注明出处!并保留本声明。感谢您的阅读和支持!
-------------本文结束感谢您的阅读-------------