MYSQL:MVCC 朴灿烈づ我的快乐病毒、 2023-01-14 01:42 163阅读 0赞 ## MVCC ## > MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,可以实现对数据库的并发访问。 ### MVCC带来的好处是? ### 多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照。 所以MVCC可以为数据库解决以下问题 * 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能 * 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题 ## MVCC的实现原理 ## MVCC的实现原理主要是依赖记录中的 `4个隐式字段,undo日志 ,Read View`来实现的。所以我们先来看看这个四个隐藏字段 ### 隐式字段 ### 每行记录除了我们自定义的字段外,还有数据库隐式定义的DB\_TRX\_ID,DB\_ROLL\_PTR,DB\_ROW\_ID等字段 * **DB\_TRX\_ID** 6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID * **DB\_ROLL\_PTR** 7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里) * **DB\_ROW\_ID** 6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB\_ROW\_ID产生一个聚簇索引 * **flag** 实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW5nMTgyMzgwMzI4OTE_size_16_color_FFFFFF_t_70] ### undo日志 ### **redo log:重做日志**,就是每次mysql在执行写入数据前先把要写的信息保存在重写日志中,但出现断电,奔溃,重启等等导致数据不能正常写入期望数据时,服务器可以通过redo\_log中的信息重新写入数据。 **undo log:撤销日志**,与redo log恰恰相反,当一些更改在执行一半时,发生意外,而无法完成,则可以根据撤消日志恢复到更改之前的壮态。 undo log主要分为两种: * **insert undo log** 事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃 * **update undo log** 事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除 ### Read View(读视图) ### 说read view之前,要先知道当前读 **当前读** 读取数据的最新版本。常见的 `update/insert/delete、还有 select ... for update、select ... lock in share mode`都是当前读。 **什么是Read View?** Read View就是在该事务进行的快照读的那一刻,会生成数据库系统当前的一个快照,这个快照就是read view。 **下面就说一下怎么基于MVCC生成Read View?** 在此之前,先说几个ReadView的概念 * **m\_ids**:**活跃事务id列表**,表示在生成ReadView时系统中活跃的读写事务的事务id列表。 * **min\_trx\_id**:**活跃事务id列表中最小事务ID**表示在生成ReadView时当前活跃的读写事务中最小的事务id,也就是m\_ids中的最小值。 * **max\_trx\_id**:**活跃事务id列表中最大事务ID**表示生成ReadView时系统中应该分配给下一个事务(这是解决情况1的核心点)的id值。 * **creator\_trx\_id**:**当前事务ID**表示生成该ReadView的事务的事务id。 **判断逻辑:** * 如果被访问版本的trx\_id属性值与ReadView中的creator\_trx\_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。 * 如果被访问版本的trx\_id属性值小于ReadView中的min\_trx\_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。 * 如果被访问版本的trx\_id属性值大于或等于ReadView中的max\_trx\_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。 * 如果被访问版本的trx\_id属性值在ReadView的min\_trx\_id和max\_trx\_id之间,那就需要判断一下trx\_id属性值是不是在m\_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。 **MVCC 解决了幻读了没有?** MVCC 解决了部分幻读,但并没有完全解决幻读。 对于快照读,MVCC 因为因为从 ReadView 读取,所以必然不会看到新插入的行,所以天然就解决了幻读的问题。 而对于当前读的幻读,MVCC 是无法解决的。需要使用 Gap Lock 或 Next-Key Lock(Gap Lock + Record Lock)来解决。 **Repeatable Read 解决了幻读是什么情况?** SQL 标准中规定的 RR 并不能消除幻读,但是 MySQL InnoDB 的 RR 可以,靠的就是 Gap 锁。在 RR 级别下,Gap 锁是默认开启的,而在 RC 级别下,Gap 锁是关闭的。 ## 深入思考 ## > 只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。 q:如果默认select产生的事务id为0的话,那么min\_trx\_id永远为0,这不是扯犊子吗? A:m\_ids为读写事务列表,而只有select查询的情况下,为只读事务,不会参与计算,并且其实只读事务会产生一个随机事务id,并不是默认为0的事务 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW5nMTgyMzgwMzI4OTE_size_16_color_FFFFFF_t_70 1] ### 场景举例 ### CREATE TABLE `test` ( `id` int(11) DEFAULT NULL, `name` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; RR级别 **情况1:** <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>事务A</td> <td>事务B</td> </tr> <tr> <td>begin;</td> <td></td> </tr> <tr> <td></td> <td>begin;</td> </tr> <tr> <td>Select * from test;</td> <td></td> </tr> <tr> <td></td> <td>insert into test(id,name) values(2,‘323’);</td> </tr> <tr> <td></td> <td>commit;</td> </tr> <tr> <td>Select * from test;</td> <td></td> </tr> <tr> <td>此时无法查询到B事务插入的数据</td> <td></td> </tr> </tbody> </table> **情况2:** <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>事务A</td> <td>事务B</td> </tr> <tr> <td>begin;</td> <td></td> </tr> <tr> <td></td> <td>begin;</td> </tr> <tr> <td></td> <td>insert into test(id,name) values(2,‘323’);</td> </tr> <tr> <td></td> <td>commit;</td> </tr> <tr> <td>Select * from test;</td> <td></td> </tr> <tr> <td>可以查询到B事务插入的数据</td> <td></td> </tr> </tbody> </table> **情况3:** <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>事务A</td> <td>事务B</td> </tr> <tr> <td>begin;</td> <td></td> </tr> <tr> <td></td> <td>begin;</td> </tr> <tr> <td>Update test set name = ‘2’ where id = 4</td> <td></td> </tr> <tr> <td></td> <td>insert into test(id,name) values(2,‘323’);</td> </tr> <tr> <td></td> <td>commit;</td> </tr> <tr> <td>Select * from test;</td> <td></td> </tr> <tr> <td>可以查询到B事务插入的数据</td> <td></td> </tr> </tbody> </table> **情况4:** <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>事务A</td> <td>事务B</td> </tr> <tr> <td>begin;</td> <td></td> </tr> <tr> <td></td> <td>begin;</td> </tr> <tr> <td>Select * from test;</td> <td></td> </tr> <tr> <td></td> <td>insert into test(id,name) values(2,‘323’);</td> </tr> <tr> <td></td> <td>commit;</td> </tr> <tr> <td>Select * from test;</td> <td></td> </tr> <tr> <td>Update test set name = ‘2’ where id = 2</td> <td></td> </tr> <tr> <td>Select * from test;</td> <td></td> </tr> <tr> <td>此时无法查询到B事务插入的数据</td> <td></td> </tr> </tbody> </table> [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW5nMTgyMzgwMzI4OTE_size_16_color_FFFFFF_t_70]: /images/20221022/61618cd4432e494aaad26b4e13a460ea.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppYW5nMTgyMzgwMzI4OTE_size_16_color_FFFFFF_t_70 1]: /images/20221022/343bd7fb62c94358a34008a97dba5546.png
相关 mysqlmvcc-csdn 既然MySQL中InnoDB使用MVCC,为什么REPEATABLE-READ不能消除幻读 第一个问题: 新版本的mysql通过mvcc解决了幻读的问题,所以你没有看到 小咪咪/ 2023年09月25日 10:14/ 0 赞/ 134 阅读
相关 mysqlmvcc解决幻读 求数据库大神,mysql事务隔离级别repeatable-read 详解 术式之后皆为逻辑,一切皆为需求和实现。希望此文能从需求、现状和解决方式的角度帮大家理解隔离级别。 叁歲伎倆/ 2023年09月25日 10:14/ 0 赞/ 165 阅读
还没有评论,来说两句吧...