事务

事务

一、概述

  • 事务是一组不可被分割执行的SQL语句集合
  • 事务成功整体提交 commit,失败整体回滚 rollback
  • 事务应该尽可能短,因为长事务会导致长时间无法释放表内行级锁,从而降低系统并发的性能
// 示例:从 A账户 转账给 B账户
// 1.A账户扣款
// 2.B账户增加
// 两个操作是一个整体,要么全部成功,要么全部失败;否则会造成数据的不一致
  • 其他相关
  • autocommit 自动提交
  • MVCC:set autocommit = 1 ; 操作事务完毕后再打开自动提交操作, 避免一致性非阻塞读;即当前select的结果是上一次查询结果的快照

二、事务相关操作命令

  • 开启事务命令
// MySQL
// 存储引擎:InnoDB

// 1.显示声明开启事务
// 开启事务
begin || start transaction ;
// 结束事务 
commint ; // 提交 
rollback ; // 回滚
// 说明:
// 在事务显示开启后,设置 autocommit 失效

// 2.关闭自动提交,手动提交一组相关的SQL操作
// 关闭自动提交
set autocommit = 0 ; 
commit ; 或 rollback ; // 结束事务
// 说明: 
// 当 autocommit = 1 时每次SQL操作都会自动提交,一组SQL操作就不是一个整体,而是一个一个的单独个体,达不到整体回滚或提交的目的
  • 查询事务隔离级别命令
// 查询事务隔离级别
SELECT @@global.tx_isolation; // 全局 
SELECT @@session.tx_isolation; // 当前会话
SELECT @@tx_isolation; // 会话中下一个事务的隔离级别

// 示例:
mysql> select @@tx_isolation ;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set
  • 设置事务隔离级别命令
// 设置事务的隔离级别

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

// 举例:设置当前会话中下一个(未开始的)事务的隔离级别为可重复读
mysql> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ ;
Query OK, 0 rows affected

// 命令参数说明如下:
命令参数解释注意事项
GLOBAL在全局对从那点开始创建的所有新连接(除了不存在的连接)设置默认事务级别需要SUPER权限
SESSION为将来在当前连接上执行的事务设置默认事务级别任何客户端都能自由改变会话隔离级别(甚至在事务的中间)
默认(无参数)设置当前会话中下一个(未开始的)事务的隔离级别任何客户端都能自由为下一个事务设置隔离级别

三、事务性质

  • 原子性(Atomicity):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不执行
  • 一致性(Consistency):在事务开始和完成时,数据都必须保持一致状态;这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的;即数据库在事务执行前后状态都必须是稳定的;无非法数据操作,如:外键约束、事务回滚、唯一性
  • 隔离性((Isolation):事务之间不会相互影响;事务是独立运行的,一个事务的操作如果影响了另一个事物,那么另一个事务就会撤回执行,要做到事务100%的隔离,需要牺牲速度和性能
  • 持久性(Durability):事务执行成功后必须全部写入磁盘;当数据库崩溃之后,InnoDB数据库表驱动会利用日志文件进行数据的重构修改,需要注意的是:安全性和性能速度不可兼得。

四、事务性质的实现原理

  • undo 日志:记录数据未更新操作前的数据
    与redo log相反,undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。undo buffer与redo buffer一样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此。
  • redo 日志:记录数据更新后的数据
    redo log就是保存执行的SQL语句到一个指定的Log文件,当Mysql执行recovery时重新执行redo log记录的SQL操作即可。当客户端执行每条SQL(更新语句)时,redo log会被首先写入log buffer;当客户端执行COMMIT命令时,log buffer中的内容会被视情况刷新到磁盘。redo log在磁盘上作为一个独立的文件存在,即Innodb的log文件。
  • 原子性、一致性、隔离性,通过 undo 日志实现
  • 持久性通过 redo 日志实现

五、多事务运行并发问题常见分类

  • 第一类丢失更新(Lost Update):撤销一个事务时, 把其他事务已经提交的更新数据覆盖
// 示例:两个事务更新同一数据,第一个事务被提交,第一个事务被撤销,会把第一个事务所做的更新也撤销
时间事务一事务二
T1开启事务-
T2-开启事务
T3查询当前账户余额为1000元-
T4-查询当前账户余额为1000元
T5-存入100元,当前账户余额为1100元
T6-提交事务
T7取出100,当前账户余额为900元-
T8撤销事务,数据回滚,当前账户余额恢复1000元-

// 上述操作过程为理论上的模拟;InnoDB 默认事务隔离级别为 可重复读,未模拟出上述过程
// 此情况在事务中不可能出现, 因为一个事务中修改时此记录已加锁, 必须等待此事务完成后另一个事务才可以继续UPDATE;详情见示例二

示例二


  • 第二类丢失更新(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题–最后的更新覆盖了由其他事务所做的更新。
// 示例: 一个事务覆盖另一个事务已提交的更新数据
时间事务一事务二
T1开启事务-
T2-开启事务
T3查询当前账户余额为1000元-
T4-查询当前账户余额为1000元
T5取出100,当前账户余额为900元-
T6提交事务-
T7-存入100元,当前账户余额为1100元
T8-提交事务

// 事务二在更新某数据状态前事务一已完成对该状态的更新并提交,事务二不知道事务一已经更新过了,再次提交,覆盖了事务一的更新结果

示例七


  • 脏数据(Dirty Reads):一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”。即读未提交:事务还没提交,他的修改已经被其他事务看到;
// 示例: 事务二查询到事务一未提交数据,事务二根据此数据进行操作,事务一紧接着撤销数据,导致事务二操纵的是“脏数据”
时间事务一事务二
T1开启事务-
T2-开启事务
T3查询当前账户余额为1000元-
T4存入100元,当前账户余额为1100元-
T5-查询当前账户余额为1100元
T6-取出100,当前账户余额为1000元
T7-提交事务
T8撤销事务-

// 事务的隔离级别:未提交读,即事务一未提交事务,事务二种读取到了事务一中的修改后的数据;详情见示例三

示例三


  • 不可重复读(Non-Repeatable Reads):同一事务中两个相同SQL读取的内容可能不同。两次读取之间其他事务提交了修改可能会造成读取数据不一致
// 示例:一个事务查询到另一事务已提交的对数据的更新
时间事务一事务二
T1开启事务-
T2-开启事务
T3查询当前账户余额为1000元-
T4存入100元,当前账户余额为1100元-
T5-查询当前账户余额为1100元
T6-提交事务
T7查询当前账户余额为1100元-
T8提交事务-

// 事务的隔离级别:提交读,即事务一中两次同样查询条件的查询结果不一致;详情见示例四

示例四


  • 幻读(Phantom Reads):同一个事务突然发现他以前没发现的数据。和不可重复读很类似,不过修改数据改成增加数据。
// 示例:对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围。若对数据精度要求不高,则影响不大
时间事务一事务二
T1开启事务-
T2-开启事务
T3查询当天财务流水:无-
T4-存入100元,当前账户余额为1100元
T5-查询当前账户余额为1100元
T6-提交事务
T7查询当天财务流水:一笔存款100元-
T8提交事务-

// 事务的隔离级别:可重复读,事务1读取指定的where子句所返回的一些行。然后,事务2插入一个新行,这个新行也满足事务1使用的查询where子句。然后事务1再次使用相同的查询读取行,但是现在它看到了事务2刚插入的行。这个行被称为幻象,因为对事务1来说,这一行的出现是不可思议的。详情见示例五

示例五


  • 不可重复读与幻读的区别:前者是同一个事务中的两次查询的结果中某一属性字段的值前后不一致,update操作;后者是同一个事务中的两次查询的结果中数据的条数不一致,insert/delete操作

六、事务的隔离级别

概述:

  • 为解决多事务运行并发问题,设置事务的隔离级别
  • 事务的隔离性是通过锁机制实现的
  • 锁机制是通过锁定索引实现,如果查询条件中有主键则锁定主键,如果有索引则先锁定对应索引然后再锁定对应的主键(可能造成死锁),如果连索引都没有则会锁定整个数据表
  • 有索引则先锁定对应索引然后再锁定对应的主键:InnoDB的索引实现方式,辅助索引存储的内容是主键索引,主键索引与数据存放在同一个文件中,以主键索引构建成一个B+树,数据存放B+树的叶子终端节点上。

四种隔离级别从低到到依次为:

  • 未提交读(READ UNCOMMIT):允许某个事务看到其他事务并没有提交的数据。可能会导致脏读、不可重复读、幻影数据。
  • 实现原理:READ UNCOMMIT不会采用任何锁。一般不推荐使用
    示例三

  • 提交读(READ COMMIT):READ COMMIT允许某个事务看到其他事务已经提交的数据。可能会导致不可重复读和幻影数据。
  • 实现原理:数据的读是不加锁的,但是数据的写入、修改、删除加锁,避免了脏读。
    示例四

  • 可重复读(REPEATABLE READ):确保同一事务的多个实例在并发读取数据时,会看到同样的数据行
  • 实现原理:数据的读、写都会加锁,当前事务如果占据了锁,其他事务必须等待本次事务提交完成释放锁后才能对相同的数据行进行操作。
  • mysql的InnoDB存储引擎通过多版本并发控制(Multi_Version Concurrency Control, MVCC)机制来解决该问题。在该机制下,事务每开启一个实例,都会分配一个版本号给它,如果读取的数据行正在被其它事务执行DELETE或UPDATE操作(即该行上有排他锁),这时该事物的读取操作不会等待行上的锁释放,而是根据版本号去读取行的快照数据(记录在undo log中),这样,事务中的查询操作返回的都是同一版本下的数据,解决了不可重复读问题。
    示例五

  • 串行(SERIALIZABLE):这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。只要操作产生了锁,就不允许其他事务读取和修改
  • 实现原理:通过强制事务排序,使之不可能相互冲突,就是在每个读的数据行加上共享锁来实现。在该隔离级别下,可以解决前面出现的脏读、不可重复读和幻读问题,但也会导致大量的超时和锁竞争现象,一般不推荐使用
    示例六

不同分离级别可能产生的问题

事务隔离级别第一类更新丢失脏读不可重复读幻读第二类更新丢失
未提交读不会
提交读不会不会
可重复读不会不会不会不会
串行不会不会不会不会不会

事务的隔离性越强,并发度越差

  • 低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销

并发控制:

  • 服务器级别控制、存储器级别控制
  • 方式:加锁(行级别锁、表级别锁)、MVCC机制多版本两阶段封锁协议(Multiversion two-phrase locking protocal)

参考资料

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值