数据库事务

原子性(Atomic)

原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚:如果在执行的过程中发生了错误,就把已经执行的操作恢复成没有执行之前的样子。


一致性(Consistency)

一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致状态。

比如用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱加起来应该还得是5000。


持久性(Durability)

持久性意味着该次转换对应的数据库操作所修改的数据都应该在磁盘种保留下来,无论之后发生了什么事故,本次转换造成的影响都不应该丢失。


隔离性(Isolation)

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

隔离得越严实,效率就会越低,因此很多时候,都要在二者之间寻找一个平衡点。SQL 标准的事务隔离级别包括:

  • 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
  • 串行化,顾名思义是对于同一行记录,"写"会加"写锁","读"会加"读锁"。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);
image.png

若隔离级别是"读未提交", 则 V1 的值就是 2,这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。

若隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。

若隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。

若隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。

开启事务的语法

显式启动事务语句
  • begin
    begin标志着开启一个事务。
  • start transaction
    start transaction 同样标志着开启一个事务,该语句后面可以跟1个或多个修饰符
    • READ ONLY 表明当前事务是一个只读事务
    • READ WRITE 表明当前事务是一个读写事务
显示提交事务

提交语句是 commit

事务回滚

如果写了几条语句发现某条语句写错了,可以使用回滚语句是 rollback 将数据库恢复到事务执行之前的样子。

自动提交
mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

可以看到默认值是 ON,也就是说在默认情况下,如果不是按照上面的方式显示开启一个事务,那么每条语句都是一个独立的事务。

隐式提交

当使用 beginstart 开启了一个事务,或者把系统变量 'autocommit' 设置为 OFF 时,事务就不会进行自动提交。但如果我们输入了某些语句,且这些语句会导致之前的事务悄悄地提交掉。那么这种特殊地语句而导致地提交称为隐式提交。

  • 定义或者修改数据库对象的数据定义语言
  • 隐式使用或者修改 MySQL 数据库中的表
  • 事务控制关于锁定的语句
  • 加载数据的语句
  • 关于MySQL复制的一些语句
  • 其他语句

实践

隔离级别的影响

在MySQL数据库中,支持上面四种隔离级别,默认的为可重复读(Repeatable read);
在Oracle数据库中,只支持 串行化(Serializable) 和 读已提交(Read committed) 这两种级别,其中默认的为 读已提交(Read committed) 级别。

在MySQL数据库中查看当前事务的隔离级别:

SELECT @@tx_isolation;
SELECT  @@transaction_isolation;     # 8.0+ 版本 

在MySQL数据库中设置事务的隔离级别:

set [glogal | session] transaction isolation level 隔离级别名称;
set tx_isolation='隔离级别名称;'
事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
读已提交(read-committed)
可重复读(repeatable-read)
串行化(serializable)
CREATE database trans;
CREATE TABLE account
(
    id int NOT NULL,
    name char(128) NULL,
    balance int NULL
);

INSERT INTO `account` VALUES (1,'lilei',450),(2,'hanmei',16000),(3,'lucy',2400);
  • 读未提交

    # 设置隔离级别:
    mysql > set session transaction isolation level read uncommitted;
    
    # A客户端开始事务:
    mysql > start transaction;
    mysql> SELECT * FROM account;
    +----+--------+---------+
    | id | name   | balance |
    +----+--------+---------+
    |  1 | lilei  |     450 |
    |  2 | hanmei |   16000 |
    |  3 | lucy   |    2400 |
    +----+--------+---------+
    3 rows in set (0.00 sec)
    
    # B 客户端开始事务
    mysql> start transaction;
    mysql> UPDATE account set balance = balance - 50 where id = 1;
    mysql> SELECT * FROM account;
    +----+--------+---------+
    | id | name   | balance |
    +----+--------+---------+
    |  1 | lilei  |     400 |
    |  2 | hanmei |   16000 |
    |  3 | lucy   |    2400 |
    +----+--------+---------+
    3 rows in set (0.00 sec)
    
    # 虽然B的事务没有提交,但是A 已经可以查询到B更新后的数据
    mysql> SELECT * FROM account;
    +----+--------+---------+
    | id | name   | balance |
    +----+--------+---------+
    |  1 | lilei  |     400 |
    |  2 | hanmei |   16000 |
    |  3 | lucy   |    2400 |
    +----+--------+---------+
    3 rows in set (0.00 sec) 
    

    假设此时B回滚,此时客户端A执行update account set balance = balance - 50 where id = 1会发现 lilei 的balance没有变成350,居然是400。在应用程序中会觉得400-50=350。解决上面的问题就是"读已提交"隔离级别。

  • 读已提交

  • 可重复读

参考资料
[1] https://www.zhihu.com/question/31346392
[2] http://www.cnblogs.com/fjdingsd/p/5273008.html#3904071
[3] https://blog.csdn.net/whoamiyang/article/details/51901888

你可能感兴趣的:(数据库事务)