简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败,在 MySQL 中,事务支持是在引擎层实现的。

一、事务的acid分别是什么

原子性(Atomicity):事务是一个原子操作单元,其对数据的全部修改操作要么全都执行,要么全都不执行

一致性(Consistent):在事务开始和完成时,数据都必须保持一致性状态,事务结束后所有的内部数据结构都必须是正确的

隔离性(Isolation):保证事务在不受外部并发操作影响的,在独立环境执行,也就是事务处理过程中的中间状态对外部是不可见的

持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即出现系统故障也能够保持

二、并发事务处理(数据并发访问)带来的四个问题

1更新丢失

当两个或多个事务选择同一行,导致最后的更新覆盖了由其他事务所做的更新

2、脏读

脏读是指,如果事务 B 修改数据但是没提交,却被另一个事务 A 读到,但是事务 B 对该记录又一次修改,然后才提交,那么事务 A 刚才读到的就是错误的数据

3、不可重复读

事务开始前后读取数据不一致,即:事务 A 事先读取了数据,事务 B 紧接了更新了数据,并提交了事务,而事务 A 后来再次读取该数据时,数据已经发生了改变。

4、幻读

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象称为幻读

脏读是事务B里面修改了数据

幻读是事务B里面新增了数据

脏读、不可重复读、幻读其实都是数据一致性问题,必须由数据库提供一定的事务隔离机制来解决,事务的隔离越严格,并发副作用越小,但是付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上‘串行化’进行,这显然与“并发”相矛盾。

三、事务的隔离级别?

浅谈 MySQL 的事务及隔离级别

由上图可以发现有两个事务 A 和事务 B分别操作 V 的值,第一次是得倒的 V1 是 100,接下来分别分析在不同的事务隔离级别下观察 V2,V3,V4 的值分别是多少:

1)读未提交:一个事务还没提交时,它做的变更就能被别的事务看到

当事务 B 将 V1 的值从 100 改到 200 的时候,即使事务 B 没提交,紧接着事务 A 的第二次查询也可以读到 V2 = 200,之后的 V3 = 200,V4 = 200

这种隔离级别情况下,避免了更新丢失,事务 B 的修改不会丢失,但同时出现了脏读,就是事务 A 读取到了事务 B 还没有提交的“脏数据”

2)读已提交:一个事务提交之后,它做的变更才会被其他事务看到。

只有当事务 B 提交之后,事务 A 才可以看到 V1 的变化,读取 V2 的时候事务 B 还没有提交,所以 V2 = 100,但是 V3 是在事务 B 提交之后看到的,所以 V3 = 200,之后的 V4 = 200 

该隔离级别避免了脏读,但是却可能出现不可重复读。

3)可重复读:事务执行期间前后看到的数据必须是一致的;与此同时,未提交的变更对其他事务也是不可见的

那么事务 A 在提交之前的读取 V2、V3 要和第一次读取到的 V1 相同,所以 V2 = 100,V3 = 100;与此同时,V4 是事务A 提交之后的读取,同时也是事务 B 之后的读取,所以可以读取到 事务 B 中的变更,V4 = 200

避免了不可重复读取和脏读,但是有时可能出现幻读。这可以通过“共享读锁”和“排他写锁”实现。

4)串行化:对应同一行记录,写会加写锁,读会加读锁,当出现锁冲突的时候,后访问的那个必须等前一个事务执行完成,释放锁,才可以继续执行,要求事务只能一个接着一个地执行

当事务 A 开始读取 V1 的时候,就会加读锁,导致事务 B 的修改操作被阻塞,直到事务 A 提交,释放锁,所以 V2 = 100,V3 = 100,然后事务 B 进行修改,V4 读取到变更后的值是200

不仅可以避免脏读、不可重复读,还避免了幻像读。

巧记:

读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到。

读已提交:别人改数据的事务已经提交,我在我的事务中才能读到。

可重复读:别人改数据的事务已经提交,我在我的事务中也不去读。

串行:我的事务尚未提交,别人就别想改数据。

这4种隔离级别,并行性能依次降低,安全性依次提高。

对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

总结来说,存在即合理,哪个隔离级别都有它自己的使用场景,要根据自己的业务来定。比如有一种场景就需要【可重复读】这种隔离级别:假设你在管理一个个人银行账户表,一个表存了每个月月底的余额,一个表存了账单明细,这是你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定需要在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。

四、事务隔离级别的实现

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作得到前一个状态的值。

浅谈 MySQL 的事务及隔离级别

长事务:从事务开始到提交,这期间时间比较长

基于上面的说明,建议尽量不要使用长事务,长事务意味着里面会存在很老的事务视图,由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就回导致大量占用存储空间。

在 MySQL 5.5 及以前的版本,回滚日志是跟数据字段一起放在 ibdata 文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库。

五、事务启动方式

  1. 显示启动事务语句,begin 或 start transaction,配套的提交语句是 commit,回滚语句是 rollback

  2. set autocommit = 0,这个命令会将线程的自动提交关掉,意味着如果你只执行一个 select 语句,这个事务就启动了,而且不会自动提交,这个事务知道你主动执行 commit 或 rellback,或者断开连接

  3. set autocommit = 1,用 begin 显示启动的事务,可以执行 commit提交。也可以执行 commit work and chain,表示提交事务并自动启动下一个事务

此处需要注意 begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动,一致性试图是在执行第一个快照读语句时创建的。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令,一致性视图也是在执行 start transaction with consistent snapshot 语句时创建的。

 

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-11-19
  • 2021-06-28
  • 2021-06-29
  • 2021-07-09
猜你喜欢
  • 2022-02-27
  • 2021-06-04
  • 2021-07-07
  • 2022-12-23
  • 2022-01-10
相关资源
相似解决方案