MySQL数据库InnoDB存储引擎多版本控制(MVCC)实现原理分析_MySQL
文/何登成
导读:
来自网易研究院的MySQL内核技术研究人何登成,把MySQL数据库InnoDB存储引擎的多版本控制(简称:MVCC)实现原理,做了深入的研究与详细的文字图表分析,方便大家理解InnoDB存储引擎实现的多版本控制技术(简称:MVCC)。
基本知识
假设对于多版本控制(MVCC)的基础知识,有所了解。MySQL数据库InnoDB存储引擎为了实现多版本的一致性读,采用的是基于回滚段的协议。
行结构
MySQL数据库InnoDB存储引擎表数据的组织方式为主键聚簇索引。由于采用索引组织表结构,记录的ROWID是可变的(索引页分裂的时候,Structure Modification Operation,SMO),因此二级索引中采用的是(索引键值, 主键键值)的组合来唯一确定一条记录。
无论是聚簇索引,还是二级索引,其每条记录都包含了一个DELETED BIT位,用于标识该记录是否是删除记录。除此之外,聚簇索引记录还有两个系统列:DATA_TRX_ID,DATA_ROLL_PTR。DATA _TRX_ID表示产生当前记录项的事务ID;DATA _ROLL_PTR指向当前记录项的undo信息。
聚簇索引行结构(与多版本一致读有关的部分,DELETED BIT省略):
二级索引行结构:
从聚簇索引行结构,与二级索引行结构可以看出,聚簇索引中包含版本信息(事务号+回滚指针),二级索引不包含版本信息,二级索引项的可见性如何判断?下面将会给出。
Read View
InnoDB存储引擎默认的隔离级别为Repeatable Read (RR),可重复读。InnoDB存储引擎在开始一个RR读之前,会创建一个Read View。Read View用于判断一条记录的可见性。Read View定义在read0read.h文件中,其中最主要的与可见性相关的属性如下:
?123456789101112131415161718192021 |
|
简单来说,Read View记录读开始时,所有的活动事务,这些事务所做的修改对于Read View是不可见的。除此之外,所有其他的小于创建Read View的事务号的所有记录均可见。可见包括两层含义:
- 记录可见,且Deleted bit = 0;当前记录是可见的有效记录。
- 记录可见,且Deleted bit = 1;当前记录是可见的删除记录。此记录在本事务开始之前,已经删除。
测试方法:
?12345678910111213141516 |
- Insert
|
-read隔离级别
repeatable read(RR)
测试结果
update primary key
代码调用流程:
?1 | ha_innobase::update_row -> row_update_for_mysql -> row_upd_step -> row_upd -> row_upd_clust_step -> row_upd_clust_rec_by_insert -> btr_cur_del_mark_set_clust_rec -> row_ins_index_entry |
简单来说,就是将cluster index的旧记录标记位删除;插入一条新纪录。该语句执行完之后,数据结构如下:
老版本仍旧存储在聚簇索引之中,其DATA_TRX_ID被设置为1811,Deleted bit设置为1,undo中记录了前镜像的事务id = 1809。新版本DATA_TRX_ID也为1811。通过此图,还可以发现,虽然新老版本是一条记录,但是在聚簇索引中是通过两条记录来标识的。同时, 由于更新了主键,二级索引也需要做相应的更新(二级索引中包含主键项)。
update non-primary key(diff value)
更新comment字段,代码调用流程与上面有部分不同,可以自行跟踪,此处省略。更新操作执行完之后,索引结构变更如下:
从上图可见,更新二级索引的键值时,聚簇索引本身并不会产生新的记录项,而是将旧版本信息记录在undo之中。与此同时,二级索引将会产生 新的索引项,其PK值保持不变,指向聚簇索引的同一条记录。细心的读者可能会发现,二级索引页面中有一个MAX_TRX_ID,此值记录的是更新二级索引 页面的最大事务ID。通过MAX_TRX_ID的过滤,INNODB能够实现大部分的辅助索引覆盖性扫描(仅仅扫描辅助索引,不需要回聚簇索引)。具体过 滤方法,将在后面的内容中给出。
update non-primary key(same value)
最后一个测试用例,是更新comment项为同样的值。在我的测试中,更新之后的索引结构如下:
聚簇索引仍旧会更新,但是二级索引保持不变。
总结
- 无论是聚簇索引,还是二级索引,只要其键值更新,就会产生新版本。将老版本数据deleted bti设置为1;同时插入新版本。
- 对于聚簇索引,如果更新操作没有更新primary key,那么更新不会产生新版本,而是在原有版本上进行更新,老版本进入undo表空间,通过记录上的undo指针进行回滚。
- 对于二级索引,如果更新操作没有更新其键值,那么二级索引记录保持不变。
- 对于二级索引,更新操作无论更新primary key,或者是二级索引键值,都会导致二级索引产生新版本数据。
- 聚簇索引设置记录deleted bit时,会同时更新DATA_TRX_ID列。老版本DATA_TRX_ID进入undo表空间;二级索引设置deleted bit时,不写入undo。
可见性判断
主键查找
select * from test where id = 1;
- 针对测试1,如果1811(DATA_TRX_ID) 无记录返回。
- 针对测试1,如果 1811(DATA_TRX_ID) >= read_view.low_limit_id,证明被标记为删除的记录1不可见,通过DATA_ROLL_PTR回滚记录,得到DATA_TRX_ID = 1809。如果1809可见,则返回记录(1,aaa);否则无记录返回。
针对测试1,如果up_limit_id,low_limit_id都无法判断可见性,那么遍历read_view中的trx_ids,依次对比事务id,如果在DATA_TRX_ID在trx_ids数组中,则不可见(更新未提交)。
select * from test where id = 9;
- 针对测试2,如果1816可见,返回(9,ccc)。
- 针对测试2,如果1816不可见,通过DATA_ROLL_PTR回滚到1811,如果1811可见,返回(9, aaa)。
针对测试2,如果1811不可见,无结果返回。
select * from test where id > 0;
- 针对测试1,索引中, 满足条件的同一记录,有两个版本(版本1,delete bit =1)。那么是否会一条记录返回两次呢?必定不会,这是因为pk = 1的可见性与pk = 9的可见性是一致的,同时pk = 1是标记了deleted bit的版本。如果事务ID = 1811可见。那么pk = 1 delete可见,无记录返回,pk = 9返回记录;如果1811不可见,回滚到1809可见,那么pk = 1返回记录,pk = 9回滚后无记录。
总结:
- 通过主键查找记录,需要配合read_view,记录DATA_TRX_ID,记录DATA_ROLL_PTR指针共同判断。
- read_view用于判断当前记录是否可见(判断DATA_TRX_ID)。DATA_ROLL_PTR用于将当前记录回滚到前一版本。
非主键查找
select comment from test where comment > ‘ ‘;
- 针对测试2,二级索 引,当前页面的最大更新事务MAX_TRX_ID = 1816。如果MAX_TRX_ID lock_sec_rec_cons_read_sees)
-
针对测试2,二级索 引,如果当前页面不能满足MAX_TRX_ID ?
1234567 if
(clust_rec
&& (old_vers || rec_get_deleted_flag(
rec,dict_table_is_comp(sec_index->table)))
&& !row_sel_sec_rec_is_for_clust_rec(rec, sec_index, clust_rec, clust_index))
满足if判断的所有聚簇索引记录,都直接丢弃,以上判断的逻辑如下:
- 需要回聚簇索引扫描,并且获得记录
- 聚簇索引记录为回滚版本,或者二级索引中的记录为删除版本
- 聚簇索引项,与二级索引项,其键值并不相等
为什么满足if判断,就可以直接丢弃数据?用白话来说,就是我们通过二级索引记录,定位聚簇索引记录,定位之后,还需要再次检查聚簇索引记录是否仍旧是我在二级索引中看到的记录。如果不是,则直接丢弃;如果是,则返回。
根据此条件,结合查询与测试2中的索引结构。可见版本为事务1811.二级索引中的两项pk = 9都能通过聚簇索引回滚到1811版本。但是,二级索引记录(ccc,9)与聚簇索引回滚后的版本(aaa,9)不一致,直接丢弃。只有二级索引记录 (aaa,9)保持一致,直接返回。
总结:
- 二级索引的多版本可见性判断,需要通过聚簇索引完成。
- 二级索引页面中保存了MAX_TRX_ID,可以快速判断当前页面中,是否所有项均可见,可以实现二级索引页面级别的索引覆盖扫描。一般而言,此判断是满足条件的,保证了索引覆盖扫描 (index only scan)的高效性。
- 二级索引中的项,需要与聚簇索引中的可见性进行比较,保证聚簇索引中的可见项,与二级索引中的项数据一致。
疑问
- 在http://blogs.InnoDB.com/wp/2011/04/mysql-5-6-multi-threaded-purge/中, 作者提到,InnoDB存储引擎的purge操作,是通过遍历undo来实现对于标记位deleted项的回收的。如果二级索引本身标记deleted位不记录 undo,那么这个回收操作如何完成?还是说purge是通过解析redo来完成回收的?(根据下面对于purge的流程分析,此问题已解决)
Purge流程
Purge功能:
InnoDB由于要支持多版本协议,因此无论是更新,删除,都只是设置记录上的deleted bit标记位,而不是真正的删除记录。后续这些记录的真正删除,是通过Purge后台进程实现的。Purge进程定期扫描InnoDB的undo,按照先 读老undo,再读新undo的顺序,读取每条undo record。对于每一条undo record,判断其对应的记录是否可以被purge(purge进程有自己的read view,等同于进程开始时最老的活动事务之前的view,保证purge的数据,一定是不可见数据,对任何人来说),如果可以purge,则构造完整记 录(row_purge_parse_undo_rec)。然后按照先purge二级索引,最后purge聚簇索引的顺序,purge一个操作生成的旧版本完整记录。
一个完整的purge函数调用流程如下:
?123 |
row_purge_step->row_purge->trx_purge_fetch_next_rec->row_purge_parse_undo_rec ->row_purge_del_mark->row_purge_remove_sec_if_poss ->row_purge_remove_clust_if_poss
|
总结:
- purge是通过遍历undo实现的。
- purge的粒度是一条记录上的一个操作。如果一条记录被update了3次,产生3个old版本,均可purge。那么purge读取undo,对于每一个操作,都会调用一次purge。一个purge删除一个操作产生的old版本(按照操作从老到新的顺序)。
- purge按照先二级索引,最后聚簇索引的顺序进行。
- purge二级索引,通过构造出的索引项进行查找定位。不能直接针对某个二级页面进行,因为不知道记录的存放page。
- 对于二级索引设置deleted bit为不需要记录undo,因为purge是根据聚簇索引undo实现。因此二级索引deleted bit被设置为1的项,没有记录undo,仍旧可以被purge。
- purge是一个耗时的操作。二级索引的purge,需要search_path定位数据,相当于每个二级索引,都做了一次index unique scan。
- 一次delete操作,IO翻番。第一次IO是将记录的deleted bit设置为1;第二次的IO是将记录删除。
文章具体来源不详,如有知情者,请在评论中回复。

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Apple의 최신 iOS18, iPadOS18 및 macOS Sequoia 시스템 릴리스에는 사진 애플리케이션에 중요한 기능이 추가되었습니다. 이 기능은 사용자가 다양한 이유로 손실되거나 손상된 사진과 비디오를 쉽게 복구할 수 있도록 설계되었습니다. 새로운 기능에는 사진 앱의 도구 섹션에 '복구됨'이라는 앨범이 도입되었습니다. 이 앨범은 사용자가 기기에 사진 라이브러리에 포함되지 않은 사진이나 비디오를 가지고 있을 때 자동으로 나타납니다. "복구된" 앨범의 출현은 데이터베이스 손상으로 인해 손실된 사진과 비디오, 사진 라이브러리에 올바르게 저장되지 않은 카메라 응용 프로그램 또는 사진 라이브러리를 관리하는 타사 응용 프로그램에 대한 솔루션을 제공합니다. 사용자는 몇 가지 간단한 단계만 거치면 됩니다.

MySQLi를 사용하여 PHP에서 데이터베이스 연결을 설정하는 방법: MySQLi 확장 포함(require_once) 연결 함수 생성(functionconnect_to_db) 연결 함수 호출($conn=connect_to_db()) 쿼리 실행($result=$conn->query()) 닫기 연결( $conn->close())

위에 작성됨 & 저자의 개인적인 이해는 이미지 기반 3D 재구성은 입력 이미지 세트에서 객체나 장면의 3D 모양을 추론하는 어려운 작업이라는 것입니다. 학습 기반 방법은 3차원 형상을 직접 추정할 수 있는 능력으로 주목을 받았습니다. 이 리뷰 논문은 새로운, 보이지 않는 뷰 생성을 포함한 최첨단 3D 재구성 기술에 중점을 두고 있습니다. 입력 유형, 모델 구조, 출력 표현 및 훈련 전략을 포함하여 가우스 스플래시 방법의 최근 개발에 대한 개요가 제공됩니다. 해결되지 않은 과제와 앞으로의 방향에 대해서도 논의한다. 해당 분야의 급속한 발전과 3D 재구성 방법을 향상할 수 있는 수많은 기회를 고려할 때 알고리즘을 철저히 조사하는 것이 중요해 보입니다. 따라서 이 연구는 가우스 산란의 최근 발전에 대한 포괄적인 개요를 제공합니다. (엄지손가락을 위로 스와이프하세요.

PHP에서 데이터베이스 연결 오류를 처리하려면 다음 단계를 사용할 수 있습니다. mysqli_connect_errno()를 사용하여 오류 코드를 얻습니다. 오류 메시지를 얻으려면 mysqli_connect_error()를 사용하십시오. 이러한 오류 메시지를 캡처하고 기록하면 데이터베이스 연결 문제를 쉽게 식별하고 해결할 수 있어 애플리케이션이 원활하게 실행될 수 있습니다.

5일 이 사이트의 소식에 따르면 넷이즈그룹 자선재단은 최근 메이다 고속도로에서 용감한 운전자인 왕샹난과 그의 아내에게 그의 용기에 경의를 표하기 위해 10만 위안의 보너스를 수여하겠다고 발표했다. 5월 1일 오전 2시 1분쯤 메이-다롄 고속도로 차양 구간에서 산사태가 발생했다. 전방 고속도로에서 산사태를 발견한 콜드체인 트럭 운전사 왕샹난(Wang Xiangnan)과 그의 아내는 즉시 급회전하기로 결정하고 12.5미터 길이의 냉동 트럭을 적시에 멈춰 40~50대의 차량을 도로에 세웠습니다. 인생의 중요한 순간에 다른 사람들을 구했습니다. 이에 앞서 중국 직원 발전 재단과 FAW Jiefang Automobile Co., Ltd.는 공동으로 그에게 10,000위안의 긍정적 에너지 보너스를 수여했으며 Ali Charity Tiantian Positive Energy는 Wang Xiangnan에게 인증서와 10,000위안의 공공 복지 기금을 수여했습니다. 그 밖에도 윤만만 외

넷이즈와 블리자드의 결합이 국내 향수 시장에 급격한 변화를 가져올 수 있다고는 누구도 생각하지 못했을 것이다. 주요 게임 제조사들은 노력을 기울여 블리자드의 부재자 트래픽의 마지막 물결을 수확하기 위해 워크래프트 전국 서버의 재개를 활용하여 연례 "여름 대결"을 5월로 앞당겨야 합니다. 오늘은 4월 향수시장을 돌아보고, 5월 향수전쟁을 기대해보겠습니다. 넷이즈와 블리자드가 성공적으로 재결합했다. 4월 10일 10시 넷이즈는 블리자드와 마이크로소프트와의 더욱 폭넓은 파트너십을 공식 발표하며 '월드 오브 워크래프트'와 '월드 오브 워크래프트'를 선보일 것을 약속했다. 워크래프트'를 올 여름부터 게임에 선보일 예정이며, '하스스톤', '디아블로' 등의 게임이 중국 서버에 다시 출시돼 플레이어들에게 더 나은 게임 경험을 제공하고 있다. 하지만 각계의 삼촌들이 워크래프트를 폭로하기 시작했습니다.

텐센트 게임즈는 5월 28일 20시에 새해 텐센트 게임 컨퍼런스가 시작된다고 공식 발표했습니다. 생방송 표지 이미지로 볼 때, Honor of Kings, Yuanmeng, League of Legends의 모바일 게임 외에도 여기에는 또한 포함됩니다 신규 게임 DNF 모바일 게임, 테레시 월드, 소드 스피릿 등

Go 표준 라이브러리 데이터베이스/sql 패키지를 통해 MySQL, PostgreSQL 또는 SQLite와 같은 원격 데이터베이스에 연결할 수 있습니다. 데이터베이스 연결 정보가 포함된 연결 문자열을 생성합니다. sql.Open() 함수를 사용하여 데이터베이스 연결을 엽니다. SQL 쿼리 및 삽입 작업과 같은 데이터베이스 작업을 수행합니다. 리소스를 해제하기 위해 defer를 사용하여 데이터베이스 연결을 닫습니다.
