事务 (transaction) 其实指的就是一组操做里面包含许多单一的逻辑,要么就是全部逻辑都成功 (提交),只要有一个逻辑没有成功,那么这一组操做就算失败,全部的数据都回归到最初的状态(回滚)。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 。
原子性 (Atomicity):事务是最小单位,不可再分。
一致性 (Consistency):事务要求全部的 DML 语句操做的时候,必须保证同时成功或者同时失败。
隔离性 (Isolation):事务 A 和事务 B 之间具备隔离性。
持久性 (Durability):是事务的保证,事务终结的标志(内存的数据持久到硬盘文件中)。
事务 A 和事务 B 之间具备必定的隔离性,有 4 个隔离级别。
- 事务 A 和事务 B,事务 A 未提交的数据,事务 B 能够读取到。
- 这里读取到的数据叫作“脏数据”。
- 这种隔离级别最低,这种级别通常是在理论上存在,数据库隔离级别通常都高于该级别。
- 事务 A 和事务 B ,事务A提交的数据,事务 B 才能读取到。
- 这种隔离级别高于读未提交。
- 换句话说,对方事务提交以后的数据,我当前事务才能读取到。
- 这种级别能够避免“脏数据”。
- 这种隔离级别会致使“不可重复读取”。
- Oracle 默认隔离级别。
- 事务 A 和事务 B,事务 A 提交以后的数据,事务 B 读取不到。
- 事务 B 是可重复读取数据。
- 这种隔离级别高于读已提交。
- 换句话说,对方提交以后的数据,我仍是读取不到。
- 这种隔离级别能够避免“不可重复读取”,达到可重复读取。
- 好比 1 点和 2 点读到数据是同一个。
- Mysql 默认级别。
- 虽然能够达到可重复读取,可是会致使“幻像读”。
- 事务 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 [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
这种方式也不会丢失更新,究其根底其实它的原理仍是和悲观锁相同:就是保证事务能在最新的数据基础上更新。