MySQL 基础教程

MySQL 高级教程

MySQL SQL 语句

MySQL 笔记

MySQL 事务


事务是并发控制的基本单位。MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些一系列的数据库操作语句就构成一个事务。

事务的特点

  • 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
  • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
  • 事务用来管理 INSERTUPDATEDELETE 语句

事务的基本属性

一般来说,事务是必须满足4个条件(ACID),原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable)。

  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGINSTART TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。

事务的隔离级别

数据库的隔离性和一致性其实是一个需要开发者去权衡的问题,为数据库提供什么样的隔离性层级也就决定了数据库的性能以及可以达到什么样的一致性;在 SQL 标准中定义了四种数据库的事务的隔离级别:READ UNCOMMITEDREAD COMMITEDREPEATABLE READSERIALIZABLE;每个级别都有字符和对应的数字编号:

  • RAED UNCOMMITED:数字编号为0,使用查询语句不会加锁,可能会读到未提交的行,称之为脏读(Dirty Read);
  • READ COMMITED:数字编号为1,只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果,称之为不可重复读(Non-Repeatable Read);
  • REPEATABLE READ:数字编号为2,多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生幻读(Phantom Read);
  • SERIALIZABLE:数字编号为3,InnoDB 隐式地将全部的查询语句加上共享锁,解决了幻读的问题。
  脏读(Dirty Read) 不可重复读(Non Repeatable Read) 幻读(Phantom Read)
Read Uncommitted(未提交读) × × ×
Read Commited(提交读) × ×
Repeatable Read(可重复读) ×
Serializable(串行读)

不可重复读和幻读的区别

两者产生的结果都是前后数据不一致,但是两者是有区别的。不可重复读主要是针对UPDATEDELETE来说,而幻读则是针对INSERT来说:

  • 不可重复读的问题示例:事务A进行查询,事务未提交。而事务B此时对记录进行UPDATE,并提交事务。当事务A再次查询(事务未提交的时候),发现两次数据不一样;
  • 幻读问题的示例:事务A查询表中的所有记录数,未提交事务,此时事务B INSERT了一条数据,提交事务。当事务A再次查询(事务未提交的时候),发现多了一条数据。

不可重复读和幻读的解决:

  • 不可重复读可以通过针对查询的那条数据加锁,即行锁,保证只有当前事务能够对这条记录进行更新,删除;也可以将事务隔离级别提升一个级别至可重复读;
  • 幻读的实际场景中,通过多版本并发控制(MVCC),主要是通过在每行记录后面保存两个隐藏的列来实现的,其中一列存放记录的版本号,查询时查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的;除了MVCC也可以通过锁住整张表(没有实际操作意义,并发低)或提升隔离级别至串行读。

可以通过以下命令 查看/设置 全局/会话 的事务隔离级别:

mysql> SELECT @@global.tx_isolation, @@session.tx_isolation, @@tx_isolation;
+-----------------------+------------------------+-----------------+
| @@global.tx_isolation | @@session.tx_isolation | @@tx_isolation  |
+-----------------------+------------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ        | REPEATABLE-READ |
+-----------------------+------------------------+-----------------+
1 row in set (0.00 sec)

# 设定全局的隔离级别,设定会话的隔离级别将 global 替换为 session 即可

SET global tx_isolation = 0;
SET global tx_isolation = 'READ-UNCOMMITTED';

SET global tx_isolation = 1;
SET global tx_isolation = 'READ-COMMITTED';

SET global tx_isolation = 2;
SET global tx_isolation = 'REPEATABLE-READ';

SET global tx_isolation = 3;
SET global tx_isolation = 'SERIALIZABLE';

注意:tx_isolation 在 5.7.20 版本之后不建议使用,在 MySQL 8.0 之后完全去掉;从 5.7.20 版本开始,建议使用新的系统变量  transaction_isolation

事务控制语句

  • BEGINSTART TRANSACTION;显式地开启一个事务;

  • COMMIT;也可以使用COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改成为永久性的;

  • ROLLBACK;有可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;

  • SAVEPOINT identifierSAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT

  • RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;

  • ROLLBACK TO identifier;把事务回滚到标记点;

  • SET TRANSACTION;用来设置事务的隔离级别。InnoDB存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE。

事务处理方法

MySQL 事务处理主要有两种方法:

1、用 BEGIN, ROLLBACK, COMMIT来实现

  • BEGIN 开始一个事务
  • ROLLBACK 事务回滚
  • COMMIT 事务确认提交

2、直接用 SET 来改变 MySQL 的自动提交模式:

  • SET AUTOCOMMIT=0 禁止自动提交
  • SET AUTOCOMMIT=1 开启自动提交

示例 

mysql> use test;
Database changed
mysql> CREATE TABLE transaction_test( id int(5)) engine=innodb;  # 创建数据表
Query OK, 0 rows affected (0.04 sec)
 
mysql> select * from transaction_test;
Empty set (0.01 sec)
 
mysql> begin;  # 开始事务
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into transaction_test value(1);
Query OK, 1 rows affected (0.01 sec)
 
mysql> insert into transaction_test value(2);
Query OK, 1 rows affected (0.00 sec)
 
mysql> commit; # 提交事务
Query OK, 0 rows affected (0.01 sec)
 
mysql>  select * from transaction_test;
+------+
| id   |
+------+
| 1    |
| 2    |
+------+
2 rows in set (0.01 sec)
 
mysql> begin;    # 开始事务
Query OK, 0 rows affected (0.00 sec)
 
mysql>  insert into transaction_test values(3);
Query OK, 1 rows affected (0.00 sec)
 
mysql> rollback;   # 回滚
Query OK, 0 rows affected (0.00 sec)
 
mysql>   select * from transaction_test;   # 因为回滚所以数据没有插入
+------+
| id   |
+------+
| 1    |
| 2    |
+------+
2 rows in set (0.01 sec)