建设童装网站的意义,seo技术教程,网站建设首页步骤,欢迎访问中国建设银行网上银行网站来源 |程序通事责编 | Carol封图 | CSDN 付费下载自视觉中国P0 事故#xff1a;余额多扣#xff01;这是一个真实的生产事件#xff0c;事件起因如下#xff1a;现有一个交易系统#xff0c;每次产生交易都会更新相应账户的余额#xff0c;出账扣减余额#xff0c;入账增… 来源 |程序通事责编 | Carol封图 | CSDN 付费下载自视觉中国P0 事故余额多扣这是一个真实的生产事件事件起因如下现有一个交易系统每次产生交易都会更新相应账户的余额出账扣减余额入账增加余额。为了保证资金安全余额发生扣减时需要比较现有余额与扣减金额大小若扣减金额大于现有余额扣减余额不足扣减失败。账户表省去其他字段结构如下CREATE TABLE account
(id bigint(20) NOT NULL,balance bigint(20) DEFAULT NULL,PRIMARY KEY (id)) ENGINE InnoDBDEFAULT CHARSET utf8mb4COLLATE utf8mb4_bin;扣减余额时sql 语序如下所示更新余额 sql 语序ps:看到上面的语序有没有个小问号为什么相同查询了这么多次其实这些 SQL 语序并不在同个方法内并且有些方法被抽出复用所以导致一些相同查询结果没办法往下传递所以只得再次从数据库中查询。为了防止并发更新余额在 t3 时刻使用写锁锁住该行记录。若加锁成功其他线程的若也执行到 t3,将会被阻塞直到前一个线程事务提交。t5 时刻进入到下一个方法再次获取账户余额然后在 Java 方法内比较余额与扣减金额若余额充足在 t7 时刻执行更新操作。上面的 SQL 语序看起来没有什么问题吧实际也是这样的账户系统已经在生产运行很久没出现什么问题。但是这里需要说一个前提系统数据库是 Oracle 。但是从上面表结构可以得知此次数据库被切换成 MySQL系统其他任何代码以及配置都不修改sql 存在小改动。就是这种情况下并发执行发生余额多扣即实际余额明明小于扣减金额但是却做了余额更新操作最后导致余额变成了负数。下面我们来重现并发这种情况假设有两个事务正在发执行该语序执行顺序如图所示。注意点数据库使用的是 MySQL默认事务隔离等级即 RR。数据库记录为 id1 balance1000假设只有当时只有这两个事务在执行。各位读者可以先思考一下t2,t3,t4,t5,t6,t11 时刻余额多少。下面贴一下事务隔离等级RR 下的答案。事务 1 的查询结果为t2 (1,1000)t4 (1,1000)t6 (1,1000)事务 2 的查询结果为t3 (1,1000)t5 (1,900)t11 (1,1000)有没有跟你想的结果的一样接着将事务隔离等级修改成 RC同样再来思考一下 t2,t3,t4,t5,t6,t11 时刻余额。再次贴下事务隔离等级RC 下的答案。事务 1 的查询结果为t2 (1,1000)t4 (1,1000)t6 (1,1000)事务 2 的查询结果为t3 (1,1000)t5 (1,900)t11 (1,900)事务 1 的查询结果大家应该会没有什么问题主要疑问点应该在于事务 2为什么换了事务隔离等级结果却不太一样下面我们先带着疑问了解一下 MySQL 的相关原理 看完你就会明白这一切。MVCC一致性视图快照读与当前读MVCC我们先来看下一个简单的例子事务隔离等级为 RR id1 balance1000更新时序事务 1 将 id1 记录 balance 更新为 900接着事务 2 在 t5 时刻查询该行记录结果很显然该行记录应该为 id1 balance1000。如果 t5 查询最新结果 id1 balance900这就读取到事务 1 未提交的数据显然不符合当前事务隔离级别。从上面例子可以看到 id1 的记录存在两个版本事务 1 版本记录为 balance1000 事务 2 版本记录为 balance900。上述功能MySQL 使用 MVCC 机制实现功能。MVCCMultiversion concurrency control多版本并发控制。摘录一段淘宝数据库月报的解释多版本控制: 指的是一种提高并发的技术。最早的数据库系统只有读读之间可以并发读写写读写写都要阻塞。引入多版本之后只有写写之间相互阻塞其他三种操作都可以并行这样大幅度提高了 InnoDB 的并发度。在内部实现中与 Postgres 在数据行上实现多版本不同InnoDB 是在 undolog 中实现的通过 undolog 可以找回数据的历史版本。找回的数据历史版本可以提供给用户读(按照隔离级别的定义有些读请求只能看到比较老的数据版本)也可以在回滚的时候覆盖数据页上的数据。在 InnoDB 内部中会记录一个全局的活跃读写事务数组其主要用来判断事务的可见性。可以看到 MVCC 主要用来提高并发还可以用来读取老版本数据。在学习 MVCC 原理之前首先我们需要了解 MySQL 记录结构。行记录如上图所示account 表一行记录除了真实数据之外还会存在三个隐藏字段用来记录额外信息。DB_TRX_ID:事务 id。DB_ROLL_PTR: 回滚指针指向 undolog。ROW_ID行 id,与此次无关。MySQL InnoDB 里面每个事务都会有一个唯一事务 ID它在事务开始的时候会跟 InnoDB 的事务系统申请的并且严格按照顺序递增的。每次事务更新数据时将会生成一个新的数据版本然后会把当前的事务 id 赋值给当前记录的 DB_TRX_ID。并且数据更新记录1,1000----1,900将会记录在 undo log回滚日志中然后使用当前记录的 DB_ROLL_PTR 指向 und olog。这样 MySQL 就可以通过 DB_ROLL_PTR 找到 undolog 推导出之前版本记录内容。查找过程如下查找过程若需要知道 V1 版本记录首先根据当前版本 V3 的 DB_ROLL_PTR 找到 undolog然后根据 undolog 内容计算出上一个版本 V2。以此类推最终找到 V1 这个版本记录。V1V2 并不是物理记录没有真实存在仅仅具有逻辑意义。一行数据记录可能同时存在多个版本但并不是所有记录都能对当前事务可见。不然上面 t5 就可能查询到最新的数据。所以查找数据版本时候 MySQL 必须判断数据版本是否对当前事务可见。一致性视图MySQL 会在事务开始后建立一个一致性视图并不是立刻建立在这个视图中会保存所有活跃的事务(还未提交的事务)。假设当前事务保存活跃事务数组为如下图。视图数组判断版本对于当前事务是否可见时基于以下规则判断若版本事务 id 小于当前活跃事务 id 数组最小值,比如版本 id 为 40小于活跃数组最小值 45。这就代表当前版本的事务已提交当前版本对于当前事务可见。若版本事务 id 大于当前活跃事务数组的最大值如版本事务 id 为 100 大于数组最大事务 id 90。说明了这个版本是当前事务创建之后生成所以这个版本对于当前事务不可见。若版本事务 id 是当前活跃数组事务之一比如版本事务 id 为 56。代表记录版本所属事务还未提交所以该版本对于当前事务不可见。若版本事务 id 不是当前活跃数组事务之一但是事务 id 位于活跃数组最小值与最大值之一比如如事务 ID 57。代表当前记录事务已提交所以该版本对于当前事务可见。若版本事务 id 为当前事务 id代表该行数据是当前事务变更的当然得可见。4 这个规则可能比较绕结合上面图片比较好理解。以上判断规则可能比较抽象看不懂没事我们再用大白话解释一下未提交事务生成的记录版本不可见。视图生成前已提交事务生成记录版本可见。视图生成后新事务生成记录版本不可见。自身事务更新永远可见。一致性视图只会在 RR 与 RC 下才会生成对于 RR 来说一致性视图会在第一个查询语句的时候生成。而对于 RC 来说每个查询语句都会重新生成视图。当前读与快照读MySQL 使用 MVCC 机制可以读取之前版本数据。这些旧版本记录不会且也无法再去修改就像快照一样。所以我们将这种查询称为快照读。当然并不是所有查询都是快照读select .... for update/ in share mode 这类加锁查询只会查询当前记录最新版本数据。我们将这种查询称为当前读。问题分析讲完原理之后我们回过头分析一下上面查询结果的原因。这里我们将上面答案再贴过来。事务隔离级别为 RRt2t3 时刻两个事务由于查询语句分别建立了一致性视图。t4 时刻由于事务 1 使用 select.. for update 为 id1 这一行上了一把锁然后获取到最新结果。而 t5 时刻由于该行已被上锁事务 2 必须等待事务 1 释放锁才能继续执行。t6 时刻根据一致性视图不能读取到其他事务提交的版本所以数据没变。t8 时刻余额扣减 100t9 时刻提交事务。此时最新版本记录为 id1 balance900。由于事务 1 事务已提交行锁被释放t5 成功获取到锁。由于 t5 是当前读所以查询的结果为最新版本数据1,900。重点来了当前这条记录的最新版本数据为 1,900但是最新版本事务 id却是事务 2 创建之后未提交的事务位于活跃事务数组中。所以最新记录版本对于事务 2 是不可见的。没办法只能根据 undolog 去读取上一版本记录 (1,1000) 这个版本记录刚好对于事务 2 可见所以 t11 的记录为 (1,1000)。而当我们将事务隔离等级修改成 RC每次都会重新生成一致性视图。所以 t11 时刻重新生成了一致性视图这时候事务 1 已提交当前最新版本的记录对于事务 2 可见所以 t11 的结果将会变为 1,900。总结MySQL 默认事务隔离等级为 RR每一行数据InnoDB的都可以有多个版本而每个版本都有独一的事务 id。MySQL 通过一致性视图确保数据版本的可见性相关规则总结如下对于 RR 事务隔离等级普通查询仅能查到事务启动前就已经提交完成的版本数据。对于 RC 事务隔离等级普通查询可以查到查询语句启动前就已经提交完成的版本数据。当前读总是读取最新版本的数据。参考资料[1] https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.html[2] http://mysql.taobao.org/monthly/2017/12/01/[3] http://mysql.taobao.org/monthly/2018/11/04/[4] https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html6月2日20:00CSDN 创始人董事长、极客帮创投创始合伙人蒋涛携手全球顶级开源基金会主席、董事聚焦中国开源现状直面开发者在开源技术、商业上的难题你绝不可错过的开源巅峰对谈立即免费围观推荐阅读因为一个跨域请求我差点丢了饭碗没错你离分布式搜索只差一个Elasticsearch入门Python开发之Django基于Docker实现Mysql数据库读写分离、集群、主从同步详解 | 原力计划全球Python调查报告Python 2正在消亡PyCharm比VS Code更受欢迎无代码来了还要程序员吗再见Eclipse | 原力计划区块链共识算法总结 | 原力计划真香朕在看了