数据库事务了解一下

介绍

事务 (transaction) 其实指的就是一组操做里面包含许多单一的逻辑,要么就是全部逻辑都成功 (提交),只要有一个逻辑没有成功,那么这一组操做就算失败,全部的数据都回归到最初的状态(回滚)。mysql

mysql 终端演示事务

现有以下帐户表,有 id 为 1 和 2 的两个帐户:sql

假如咱们要让 id 为 1 的帐户给 id 为 2 的帐户转 100 块,咱们要执行的 sql 就是:数据库

update account set money=money-100 where id=1;
update account set money=money+100 where id=2;

执行完以后:服务器

方式一:关闭事务的自动提交

在上面示例中,其实 mysql 默认就帮咱们提交了事务,只是它把每一条执行的 sql 语句当成了一组操做,也就是执行一条 sql 事务就自动提交。session

而 mysql 事务的自动提交是能够经过 autocommit 变量设置的:并发

能够看到它默认是开启状态,如今咱们将它关闭:测试

再次执行上面的两条 sql:spa

这时候会发现表数据好像是正常修改了。其实这只是此时内存中的数据,并无持久化保存到 db,咱们能够经过 navicat 查看一下表数据:3d

此时咱们再执行一下 commit 提交事务:code

再次使用 navicat 查看表数据:

此时上述的更新操做就持久化到了 db,这就是事务的提交 (commit) 操做。

假如咱们不执行 commit,执行 rollback

能够看到数据又回到更新前的初始状态,这就是事务的回滚 (rollback) 操做。

这种修改自动提交的方式默认只是局部针对当前会话链接,对其它链接没有影响,还可设置全局自动提交以下:
set [session] autocommit=[0|1];  // 默认 设置会话级事务自动提交
set global autocommit=[0|1];  // 全局级事务自动提交

方式二:手动开启事务

除了如方式一修改 mysql 事务的自动提交这种方式,mysql 还为咱们提供了手动开始事务的方式,以下:

可经过 start transaction 开启事务:

提交和回滚事务也都是经过 commit 和 rollback 。

以上两条 DML 语句必须同时成功或者同时失败,最小单元不可再分。当第一条 DML 语句执行成功后,并不能将底层数据库中的第一个帐户的数据修改,只是将操做记录了一下,这个记录是在内存中完成的;当第二条 DML 语句执行成功后,和底层数据库文件中的数据完成同步。若第二条 DML 语句执行失败,则清空全部的历史操做记录,要完成以上的功能必须借助事务。

事务四大特征 (ACID)

原子性 (Atomicity):事务是最小单位,不可再分。

一致性 (Consistency):事务要求全部的 DML 语句操做的时候,必须保证同时成功或者同时失败。

隔离性 (Isolation):事务 A 和事务 B 之间具备隔离性。

持久性 (Durability):是事务的保证,事务终结的标志(内存的数据持久到硬盘文件中)。

事务的开启与结束标志

开启标志:
- 任何一条 DML 语句 (insert、update、delete) 执行,标志事务的开启。
结束标志:
- 提交:成功的结束,将全部的 DML 语句操做历史记录和底层硬盘数据来一次同步。
- 回滚:失败的结束,将全部的 DML 语句操做历史记录所有清空。

隔离性 (Isolation)

隔离级别

事务 A 和事务 B 之间具备必定的隔离性,有 4 个隔离级别。

  • read uncommitted(读未提交)

    - 事务 A 和事务 B,事务 A 未提交的数据,事务 B 能够读取到。

    - 这里读取到的数据叫作“脏数据”。

    - 这种隔离级别最低,这种级别通常是在理论上存在,数据库隔离级别通常都高于该级别。

  • read committed(读已提交)

    - 事务 A 和事务 B ,事务A提交的数据,事务 B 才能读取到。

    - 这种隔离级别高于读未提交。

    - 换句话说,对方事务提交以后的数据,我当前事务才能读取到。

    - 这种级别能够避免“脏数据”。

    - 这种隔离级别会致使“不可重复读取”。

    - Oracle 默认隔离级别。

  • repeatable read(可重复读)

    - 事务 A 和事务 B,事务 A 提交以后的数据,事务 B 读取不到。

    - 事务 B 是可重复读取数据。

    - 这种隔离级别高于读已提交。

    - 换句话说,对方提交以后的数据,我仍是读取不到。

    - 这种隔离级别能够避免“不可重复读取”,达到可重复读取。

    - 好比 1 点和 2 点读到数据是同一个。

    - Mysql 默认级别。

    - 虽然能够达到可重复读取,可是会致使“幻像读”。

  • serializable(串行化)

    - 事务 A 和事务 B,事务 A 在操做数据库时,事务 B 只能排队等待。

    - 这种隔离级别不多使用,吞吐量过低,用户体验差。

    - 这种级别能够避免“幻像读”,每一次读取的都是数据库中真实存在数据,事务 A 与事务 B 串行,而不并发。

- 脏读:一个事务读到了另外一个事务未提交的数据。

- 不可重复读:一个事务读到了另外一个事务已提交的数据,形成先后两次查询结果不一致。

- 幻读:一个事务读到了另外一个事务 insert 的数据,形成先后两次查询结果不一致(mysql 为 innoDB 引擎时不存在这个问题)。

隔离级别与一致性关系

隔离级别 脏读 不可重复读 幻读
读未提交 可能 可能 可能
读已提交 不可能 可能 可能
可重复读 不可能 不可能 对 InnoDB 不可能
串行化 不可能 不可能 不可能

设置事务隔离级别

  • 方式一:修改配置文件

    mysql 能够在 my.ini 文件中使用 transaction-isolation 选项来设置服务器的缺省事务隔离级别。

    值能够是:
    – READ-UNCOMMITTED – READ-COMMITTED – REPEATABLE-READ – SERIALIZABLE 例: [mysqld] transaction-isolation = READ-COMMITTED
  • 方式二:命令动态设置

    隔离级别也能够在运行的服务器中动态设置,应使用 SET TRANSACTION ISOLATION LEVEL 语句。
    语法:
    SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL <isolation-level>
    [GLOBAL]:全局级设置,对全部会话有效。
    [SESSION]:默认级别,会话级设置,只对当前会话有效。 <isolation-level>: – READ UNCOMMITTED – READ COMMITTED – REPEATABLE READ – SERIALIZABLE 例:
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

要查看当前事务隔离级别,执行 SELECT @@tx_isolation; 便可:

丢失更新问题

假若有以下帐户表:

分析:如今同时有 A 和 B 两个事务操做,A 要修改 id 为 1 的 name 为 "zhangsansan",B 要修改 id 为 1 的 money 为 "2000"。此时必然有一个事务是先提交的:

假如 A 先提交,此时 id 为 1 的 name 已修改成 "zhangsansan"。此时问题就出现了,由于 B 的修改信息如今还在内存中,B 这里的 name 仍是以前的 "zhangsan",提交后就会覆盖 A 的修改内容,最后将数据修改成:

id    money    name
1     2000   zhangsan

此时,A 的更新内容就丢失了。

悲观锁(排他锁)

用 mysql 测试上面示例,咱们会发现并无出现上述分析状况,缘由就是排他锁。

A 窗口,开启事务,更新数据,但不提交:

B 窗口,开启事务,执行更新操做,会发现阻塞住了:

当 A 窗口事务提交,B 窗口的阻塞会立马消失,接着执行完毕。这是由于在 mysql 的事务中执行更新操做时会给要更新的数据加上排他锁,若是当前更新事务未提交,那么此时其它事务对该数据的更新操做就会被阻塞至当前事务提交(排他锁释放),当前事务提交后,其它事务就能拿到最新的数据在最新数据的基础上更新,就不会出现分析中丢失更新的状况。

还能够经过查询操做主动给表或行加排他锁,例如:
select * from account where id=1 for update;

乐观锁

与悲观锁不一样,悲观锁是由数据库机制提供,而乐观锁是须要开发者手动控制的。修改表结构,给 account 表添加一个 version 字段:

只是要使用乐观锁的方式咱们就须要作一些额外的操做,例:

在事务提交以前,须要先检查当前内存行数据 version 和对应 db 实际行数据 version 是否相同,若是相同,则提交更新,若是当前 version 小于实际 version,就将当前数据更新到实际 version 对应的数据,而后在该数据的基础上执行咱们本身的更新操做,而且将 version 自增 1 后提交。

依然以上面示例说明,在 A 事务将 id 为 1 的 name 改成 "zhangsansan" 提交后,该条数据 version 版本此时就为 1。B 事务接着提交,当它以上述方式检查当前内存 version 时,会发现当前内存 version 为 0,而实际对应数据 version 为 1,它就要将内存数据更新为 version 为 1 对应的这个版本了。即 name 同步为 "zhangsan",接着在这个基础上执行本身的修改操做,将 money 修改成 2000,因此更新后的结果为:

id    money     name
1     2000   zhangsansan

这种方式也不会丢失更新,究其根底其实它的原理仍是和悲观锁相同:就是保证事务能在最新的数据基础上更新。

相关文章
相关标签/搜索