데이터베이스 시스템과 파일 시스템의 가장 큰 차이점은 데이터베이스가 작업의 원자성을 보장할 수 있다는 것입니다. 작업이 완료되지 않거나 데이터베이스가 다운되더라도 작업의 절반이 발생하지 않습니다. 이는 데이터베이스 로그이며 이를 보장하려면 완전한 충돌 복구 메커니즘이 필요합니다. 이 기사에서는 InnoDB의 충돌 복구 프로세스를 주의 깊게 분석하며 코드는 5.6 브랜치를 기반으로 합니다.
lsn: 데이터베이스가 생성된 이후 생성된 리두 로그의 양으로 이해할 수 있으며, 값이 클수록 데이터베이스에 더 많은 업데이트가 발생하는 순간으로도 이해할 수 있습니다. 업데이트의. 또한 각 데이터 페이지에는 마지막으로 수정된 lsn을 나타내는 lsn도 있습니다. 값이 클수록 나중에 수정되었습니다. 예를 들어 데이터 페이지 A의 lsn은 100, 데이터 페이지 B의 lsn은 200, 체크포인트 lsn은 150, 시스템 lsn은 300으로 현재 시스템이 300으로 업데이트 되었으며 데이터 페이지가 더 작아졌다는 의미입니다. 150개 이상이 디스크에 플러시되었으므로 페이지 A의 최신 데이터는 디스크에 있어야 하지만 데이터 페이지 B는 반드시 그런 것은 아니며 여전히 메모리에 있을 수 있습니다.
redo 로그: 최신 데이터베이스는 리두 로그를 작성해야 합니다. 예를 들어 데이터 일부를 수정하려면 먼저 리두 로그를 작성한 다음 데이터를 작성해야 합니다. 리두 로그를 작성한 후 성공이 클라이언트에 직접 반환됩니다. 이렇게 디스크를 한 번 더 쓰는 것처럼 보이지만, 디스크에 무작위 쓰기(데이터 쓰기)가 순차 쓰기(리두 로그 쓰기)로 변환되기 때문에 성능이 크게 향상된다. 데이터베이스가 중단되면 리두 로그를 스캔하여 플러시되지 않은 데이터 페이지를 찾을 수 있습니다(충돌 이전에는 데이터 페이지가 메모리에서만 수정되었을 수 있지만 디스크에 쓸 시간이 없었을 수 있음). ) 데이터가 손실되지 않도록 합니다.
실행 취소 로그: 데이터베이스는 실행 취소와 유사한 기능도 제공합니다. 잘못된 데이터를 수정한 경우 롤백 명령을 사용하여 이전 작업을 롤백할 수 있습니다. 이 기능을 지원하려면 실행 취소 로그가 필요합니다. 또한 동시성(동일한 레코드, 다른 스레드의 읽기가 충돌하지 않음, 읽기 및 쓰기가 쓰기 및 읽기와 충돌하지 않음, 동시 쓰기만 충돌)을 향상시키기 위해 모두 MVCC에서 유사한 메커니즘을 구현합니다. 이는 실행 취소 로그에도 의존합니다. 통합 관리를 위해 redo 로그와 달리 undo 로그는 버퍼 풀에 해당 데이터 페이지가 있으며, 이는 일반 데이터 페이지와 함께 관리되며 LRU 규칙에 따라 메모리에서 제거되고 디스크에서 읽혀집니다. 나중에. 일반 데이터 페이지와 마찬가지로 실행 취소 페이지를 수정하려면 먼저 리두 로그를 작성해야 합니다.
체크포인트: 영어 이름은 체크포인트 입니다. 데이터베이스의 성능을 향상시키기 위해 메모리가 수정될 때마다 데이터 페이지가 디스크에 플러시되지 않습니다. 체크포인트 이전의 데이터 페이지는 디스크로 플러시되므로 이전 로그는 쓸모가 없습니다(InnoDB 리두로그 로그의 재활용으로 인해 현재 로그의 이 부분을 덮어쓸 수 있음). 디스크로 플러시되거나 디스크 쓰기가 없을 수 있으므로 크래시 복구 중에 체크포인트 이후의 로그를 계속 사용해야 합니다. InnoDB는 더티 페이지의 새로 고침 상태를 기반으로 정기적으로 체크포인트를 진행하여 데이터베이스 충돌 복구 시간을 줄입니다. 체크포인트 정보는 첫 번째 로그 파일의 헤드에 있습니다.
충돌 복구: 사용자가 데이터를 수정하고 성공 메시지를 받았습니다. 그러나 데이터베이스의 경우 수정된 데이터가 이때 디스크에 저장되지 않았을 수 있습니다. 이때 데이터베이스가 다시 시작된 후 중단됩니다. , 데이터베이스는 사용자의 데이터가 손실되지 않도록 로그에서 수정된 데이터를 찾아 디스크에 다시 써야 합니다. 로그에서 데이터를 검색하는 이 프로세스는 응급 복구의 주요 작업이며 데이터베이스 롤포워드가 될 수도 있습니다. 물론, 크래시 복구에서는 커밋되지 않은 트랜잭션을 롤백하고 실패한 트랜잭션을 제출하는 것도 필요합니다. 롤백 작업에는 실행 취소 로그의 지원이 필요하고 실행 취소 로그의 무결성과 신뢰성을 보장하려면 다시 실행 로그가 필요하므로 충돌 복구는 먼저 다시 실행 롤포워드를 수행한 다음 실행 취소 롤백을 수행합니다.
소스 코드 관점에서 데이터베이스 충돌 복구 프로세스를 주의 깊게 분석해 보겠습니다. 엔진 초기화 단계에서 전체 프로세스가 완료됩니다. (innobase_init
),其中最主要的函数是innobase_start_or_create_for_mysql
innodb는 이 기능을 통해 크래시 복구를 포함하여 생성 및 초기화를 완료합니다. 먼저 데이터베이스의 롤포워드를 소개하겠습니다.
롤 포워드 데이터베이스는 주로 두 단계로 나뉩니다. 첫 번째는 로그 스캔 단계입니다. 스캐닝 단계에서는 데이터 페이지의 space_id 및 page_no에 따라 리두 로그를 hash_table에 배포합니다. 동일한 데이터 페이지가 동일한 해시 버킷에 배포되며 lsn 크기에 따라 작은 것부터 큰 것까지 정렬됩니다. 스캔이 완료된 후 전체 해시 테이블을 순회하며 각 데이터 페이지의 로그를 차례로 적용하고 나면 데이터 페이지의 상태는 최소한 크래시 이전 상태로 복원됩니다. 코드를 자세히 분석해 보겠습니다.
먼저 모든 ibdata 파일을 엽니다(open_or_create_data_files
)(ibdata는 여러 개를 가질 수 있음). 각 ibdata 파일에는 헤더에 flash_lsn이 있습니다. 완전히 기록되지 않았으며 복구가 필요한 데이터 후속 조치(recv_recovery_from_checkpoint_start_func
)는 checkpont_lsn과 이 두 값을 비교하여 ibdata를 롤포워드해야 하는지 여부를 결정합니다. open_or_create_data_files
)(ibdata可以有多个),每个ibdata文件有个flush_lsn在头部,计算出这些文件中的max_flush_lsn和min_flush_lsn,因为ibdata也有可能有数据没写完整,需要恢复,后续(recv_recovery_from_checkpoint_start_func
)通过比较checkpont_lsn和这两个值来确定是否需要对ibdata前滚。
接着,打开系统表空间和日志表空间的所有文件(fil_open_log_and_system_tablespace_files
),防止出现文件句柄不足,清空buffer pool(buf_pool_invalidate
)。接下来就进入最最核心的函数:recv_recovery_from_checkpoint_start_func,注意,即使数据库是正常关闭的,也会进入。
虽然recv_recovery_from_checkpoint_start_func
看过去很冗长,但是很多代码都是为了LOG_ARCHIVE特性而编写的,真正数据崩溃恢复的代码其实不多。
首先,初始化一些变量,查看srv_force_recovery
这个变量,如果用户设置跳过前滚阶段,函数直接返回。
接着,初始化recv_sys
结构,分配hash_table的大小,同时初始化flush list rbtree。recv_sys
结构主要在崩溃恢复前滚阶段使用。hash_table就是之前说的用来存不同数据页日志的哈希表,哈希表的大小被初始化为buffer_size_in_bytes/512, 这个是哈希表最大的长度,超过就存不下了,幸运的是,需要恢复的数据页的个数不会超过这个值,因为buffer poll最多(数据库崩溃之前脏页的上线)只能存放buffer_size_in_bytes/16KB个数据页,即使考虑压缩页,最多也只有buffer_size_in_bytes/1KB个,此外关于这个哈希表内存分配的大小,可以参考bug#53122。flush list rbtree这个主要是为了加入插入脏页列表,InnoDB的flush list必须按照数据页的最老修改lsn(oldest_modifcation)从小到大排序,在数据库正常运行时,可以通过log_sys->mutex和log_sys->log_flush_order_mutex保证顺序,在崩溃恢复则没有这种保证,应用数据的时候,是从第一个元素开始遍历哈希表,不能保证数据页按照最老修改lsn(oldest_modifcation)从小到大排序,这样就需要线性遍历flush_list来寻找插入位置,效率太低,因此引入红黑树,加快查找插入的位置。
接着,从ib_logfile0的头中读取checkpoint信息,主要包括checkpoint_lsn和checkpoint_no。由于InnoDB日志是循环使用的,且最少要有2个,所以ib_logfile0一定存在,把checkpoint信息存在里面很安全,不用担心被删除。checkpoint信息其实会写在文件头的两个地方,两个checkpoint域轮流写。为什么要两个地方轮流写呢?假设只有一个checkpoint域,一直更新这个域,而checkpoint域有512字节(OS_FILE_LOG_BLOCK_SIZE
다음으로 시스템 테이블스페이스 및 로그 테이블스페이스(fil_open_log_and_system_tablespace_files
)의 모든 파일을 열어 파일 핸들 부족을 방지하고 버퍼 풀을 클리어(buf_pool_invalidate
)합니다. 다음으로 가장 핵심적인 함수인 recv_recovery_from_checkpoint_start_func를 입력합니다. 데이터베이스가 정상적으로 종료되더라도 입력이 된다는 점 참고하세요. recv_recovery_from_checkpoint_start_func
가 장황해 보이지만 LOG_ARCHIVE 기능을 위해 작성된 코드가 많고 실제 데이터 충돌 복구를 위한 코드는 많지 않습니다. 🎜먼저 일부 변수를 초기화하고 srv_force_recovery
변수를 확인합니다. 사용자가 롤포워드 단계를 건너뛰도록 설정하면 함수가 직접 반환됩니다. 🎜다음으로 recv_sys
구조체를 초기화하고, hash_table의 크기를 할당하고, 플러시 리스트 rbtree를 초기화합니다. recv_sys
구조는 주로 충돌 복구의 롤포워드 단계에서 사용됩니다. hash_table은 앞서 언급한 바와 같이 다양한 데이터 페이지의 로그를 저장하는 데 사용되는 해시 테이블입니다. 해시 테이블의 크기는 buffer_size_in_bytes/512로 초기화됩니다. 이 길이를 초과하면 저장할 수 없습니다. 다행히도 버퍼 폴링(데이터베이스 충돌 전 온라인 더티 페이지)에서는 압축된 페이지를 고려하더라도 buffer_size_in_bytes/16KB 데이터 페이지만 저장할 수 있으므로 데이터 페이지 수는 이 값을 초과하지 않습니다. 최대 buffer_size_in_bytes/1KB에 불과합니다. 또한, 이 해시 테이블 메모리 할당 크기에 대해서는 bug#53122를 참조하세요. 플러시 목록 rbtree는 삽입된 더티 페이지 목록을 추가하는 데 주로 사용됩니다. InnoDB의 플러시 목록은 데이터 페이지의 가장 오래된 수정 lsn(oldest_modifcation)에 따라 정렬되어야 합니다. >mutex 및 log_sys- >log_flush_order_mutex는 충돌 복구에 대한 보장이 없습니다. 데이터를 적용할 때 해시 테이블이 첫 번째 요소에서 통과된다는 보장은 없습니다. 가장 오래된 수정 lsn(oldest_modifcation)에 따르면 삽입 위치를 찾기 위해 플러시 리스트를 선형 순회해야 하는데 이는 너무 비효율적입니다. 따라서 삽입 위치 검색 속도를 높이기 위해 레드-블랙 트리를 도입합니다. 🎜다음으로, 주로 checkpoint_lsn 및 checkpoint_no를 포함하여 ib_logfile0 헤더에서 체크포인트 정보를 읽습니다. InnoDB 로그는 주기적으로 사용되며 최소 2개가 있어야 하므로 ib_logfile0이 반드시 존재해야 체크포인트 정보가 저장되어 있어 안전하며, 삭제될 염려가 없다. 실제로 체크포인트 정보는 파일 헤더의 두 곳에 기록되며, 두 개의 체크포인트 필드가 차례로 기록됩니다. 왜 두 군데에 차례로 써야 합니까? 체크포인트 필드가 하나만 있고 이 필드는 항상 업데이트되며 체크포인트 필드의 크기는 512바이트(OS_FILE_LOG_BLOCK_SIZE
)라고 가정합니다. 이 512바이트를 쓸 때 데이터베이스가 중단되고 서버가 중단됩니다. 너무 멈춥니다(먼저 하드웨어의 원자성 쓰기 기능에 관계없이(초기 하드웨어에는 이 기능이 없었음) 512바이트 중 절반만 쓸 수 있으므로 전체 체크포인트 도메인을 사용할 수 없게 됩니다. 이런 방식으로 데이터베이스는 응급 복구를 수행할 수 없으므로 데이터베이스를 시작할 수 없습니다. 체크포인트 도메인이 두 개 있는 경우 하나가 손상되더라도 다른 하나를 사용하여 복구를 시도할 수 있습니다. 이때 로그가 덮어쓰여졌을 수도 있지만 적어도 복구 성공 확률은 높아집니다. 두 개의 체크포인트 도메인이 차례로 기록되므로 디스크 섹터 오류의 영향도 줄일 수 있습니다. checkpoint_lsn 이전의 데이터 페이지는 디스크에 배치되어 있으므로 롤포워드할 필요가 없습니다. 후속 데이터 페이지는 아직 디스크에 배치되지 않았을 수 있으므로 디스크에 배치했더라도 상관없습니다. 재실행 로그는 멱등성이기 때문에 한 번 적용되고 두 번 적용됩니다. 모두 동일합니다(기본 구현: 데이터 페이지의 lsn이 현재 재실행 로그의 lsn보다 크거나 같으면 적용되지 않습니다. checkpoint_no는 디스크에 체크포인트 도메인이 기록되는 횟수로 이해될 수 있으며, 디스크가 플러시될 때마다 1씩 증가하며 이 값은 모듈로 2로 대체 쓰기를 구현하는 데 사용할 수 있습니다. checkpoint_no 필드. 일반 논리에서는 checkpoint_no 값이 최종 체크포인트 정보로 선택되며, 이는 후속 충돌 복구 스캔의 시작점으로 사용됩니다.그런 다음 체크포인트 필드의 정보를 사용하여 recv_sys 구조의 일부 정보를 초기화한 후 로그 구문 분석recv_group_scan_log_recs
,这个函数后续我们再分析,主要作用就是解析redo日志,如果内存不够了,就直接调用应用(recv_apply_hashed_log_recs
)日志,然后再接着解析。如果需要应用的日志很少,就仅仅解析分发日志,到recv_recovery_from_checkpoint_finish
函数中在应用日志。
接着,依据当前刷盘的数据页状态做一次checkpoint,因为在recv_group_scan_log_recs
里可能已经应用部分日志了。至此recv_recovery_from_checkpoint_start_func
函数结束。
在recv_recovery_from_checkpoint_finish
函数中,如果srv_force_recovery设置正确,就开始调用函数recv_apply_hashed_log_recs
应用日志,然后等待刷脏的线程退出(线程是崩溃恢复时临时启动的),最后释放recv_sys的相关资源以及hash_table占用的内存。
至此,数据库前滚结束。接下来,我们详细分析一下redo日志解析函数以及redo日志应用函数的实现细节。
解析函数的最上层是recv_group_scan_log_recs
,这个函数调用底层函数(log_group_read_log_seg
),按照RECV_SCAN_SIZE(64KB)大小分批读取。读取出来后,首先通过block_no和lsn之间的关系以及日志checksum判断是否读到了日志最后(所以可以看出,并没一个标记在日志头标记日志的有效位置,完全是按照上述两个条件判断是否到达了日志尾部),如果读到最后则返回(之前说了,即使数据库是正常关闭的,也要走崩溃恢复逻辑,那么在这里就返回了,因为正常关闭的checkpoint值一定是指向日志最后),否则则把日志去头掐尾放到一个recv_sys->buf中,日志头里面存了一些控制信息和checksum值,只是用来校验和定位,在真正的应用中没有用。在放到recv_sys->buf之前,需要检验一下recv_sys->buf有没有满(RECV_PARSING_BUF_SIZE
,2M),满了就报错(如果上一批解析有不完整的日志,日志解析函数不会分发,而是把这些不完整的日志留在recv_sys->buf中,直到解析到完整的日志)。接下的事情就是从recv_sys->buf中解析日志(recv_parse_log_recs
)。日志分两种:single_rec和multi_rec,前者表示只对一个数据页进行一种操作,后者表示对一个或者多个数据页进行多种操作。日志中还包括对应数据页的space_id,page_no,操作的type以及操作的内容(recv_parse_log_rec
)의 핵심 기능에 들어갑니다. 해당 로그를 파싱한 후 space_id, page_no에 따라 해싱(해당 테이블스페이스가 메모리에 없으면 테이블이 삭제된 것임)하고 hash_table(로그의 실제 저장 위치)에 넣는다. 아직 버퍼 풀에 있음) 그게 다이며 후속 애플리케이션을 기다리고 있습니다. 여기에 주목할 만한 몇 가지 사항이 있습니다.
multi_rec 유형인 경우 MLOG_MULTI_REC_END 표시가 발견된 경우에만 로그가 완료된 것으로 간주되어 hash_table에 배포됩니다. 코드를 보면 multi_rec 유형의 로그를 두 번 파싱하는 것을 알 수 있는데, 한 번은 무결성을 확인하고(MLOG_MULTI_REC_END를 찾아보며), 두 번째는 로그를 배포하는 것이 최적화될 수 있는 지점이라고 느껴집니다.
현재 50가지가 넘는 로그 작업 유형이 있으며 각 작업의 내용이 다르기 때문에 길이도 다릅니다. 현재 로그 구문 분석 로직은 모든 내용을 순서대로 구문 분석한 다음 길이를 결정해야 합니다. 다음 로그의 시작 위치를 찾습니다. 이 방법은 실제로 약간 비효율적입니다. 각 작업의 헤더에 필드를 추가하여 후속 콘텐츠의 길이를 저장할 수 있습니다. 이렇게 하면 너무 많은 콘텐츠를 구문 분석할 필요가 없으므로 구문 분석 속도가 더욱 향상됩니다. 충돌 복구 속도. 결과를 보면 속도가 두 배로 늘어날 수 있습니다(38초에서 14초로, 자세한 내용은 버그 #82937 참조).
체크포인트 이후에도 로그가 남아있다면 이전에 데이터베이스가 제대로 종료되지 않았다는 의미이므로 크래시 복구가 필요하므로 추가적인 작업(recv_init_crash_recovery
recv_init_crash_recovery code>) 오류 로그에 "데이터베이스가 정상적으로 종료되지 않았습니다!" 및 "충돌 복구 시작 중"이라는 일반적인 오류를 인쇄하고, 복구가 필요한 경우 데이터 페이지가 절반만 기록되었는지 확인합니다. (buf_dblwr_process
), 또한 시작해야 합니다. 스레드는 애플리케이션 로그에 의해 생성된 더티 페이지를 플러시하는 데 사용됩니다(buf_flush_page_cleaner_thread가 현재 시작되지 않았기 때문입니다). 마지막으로 모든 테이블스페이스를 열어야 합니다. . 참고로 모두 테이블입니다. . . Alibaba Cloud RDS MySQL의 운영 및 유지 관리에서 충돌 복구 단계에서 데이터베이스가 중단되는 경우가 종종 있으며, 오류 로그에 ".ibd 파일에서 테이블스페이스 정보를 읽는 중..."과 유사한 단어가 있습니다. 데이터베이스가 모든 테이블을 열고 있다는 것을 알고 테이블 수를 살펴보면 수십, 심지어 수백만 개의 테이블이 있다는 것을 알았습니다. . . 데이터베이스가 모든 테이블을 열어야 하는 이유는 로그를 배포할 때 space_id가 어떤 ibd 파일에 해당하는지 확인해야 하기 때문입니다. 이는 모든 테이블을 열고 space_id 정보를 읽어서 결정됩니다. 또 다른 이유는 이중 쓰기를 용이하게 하기 위함입니다. 절반만 쓰여진 데이터를 확인하기 위한 버퍼입니다. 너무 많은 테이블로 인해 복구 속도가 느려지는 문제를 해결하기 위해 MySQL 5.7이 최적화되었습니다(WL#7142). 주요 아이디어는 새 로그 mlog_file_name(space_id 및 파일 이름 매핑 포함)을 작성하여 작업이 완료되었음을 나타내는 것입니다. 이 테이블에 대한 후속 작업은 이 새 로그를 작성할 필요가 없습니다. 응급 복구가 필요한 경우 mlog_file_name을 수집하여 수정된 테이블을 확인하기 위해 한 번 더 스캔을 수행할 수 있습니다. space_id를 결정하는 모든 테이블. recv_init_crash_recovery
),比如在错误日志中打印我们常见的“Database was not shutdown normally!”和“Starting crash recovery.”,还要从double write buffer中检查是否发生了数据页半写,如果有需要恢复(buf_dblwr_process
),还需要启动一个线程用来刷新应用日志产生的脏页(因为这个时候buf_flush_page_cleaner_thread还没有启动)。最后还需要打开所有的表空间。。注意是所有的表。。。我们在阿里云RDS MySQL的运维中,常常发现数据库hang在了崩溃恢复阶段,在错误日志中有类似“Reading tablespace information from the .ibd files...”字样,这就表示数据库正在打开所有的表,然后一看表的数量,发现有几十甚至上百万张表。。。数据库之所以要打开所有的表,是因为在分发日志的时候,需要确定space_id对应哪个ibd文件,通过打开所有的表,读取space_id信息来确定,另外一个原因是方便double write buffer检查半写数据页。针对这个表数量过多导致恢复过慢的问题,MySQL 5.7做了优化,WL#7142, 主要思想就是在每次checkpoint后,在第一次修改某个表时,先写一个新日志mlog_file_name(包括space_id和filename的映射),来表示对这个表进行了操作,后续对这个表的操作就不用写这个新日志了,当需要崩溃恢复时候,多一次扫描,通过搜集mlog_file_name来确定哪些表被修改过,这样就不需要打开所有的表来确定space_id了。
最后一个值得注意的地方是内存。之前说过,如果有太多的日志已经被分发,占用了太多的内存,日志解析函数会在适当的时候应用日志,而不是等到最后才一起应用。那么问题来了,使用了多大的内存就会出发应用日志逻辑。答案是:buffer_pool_size_in_bytes - 512 * buffer_pool_instance_num * 16KB。由于buffer_pool_instance_num一般不会太大,所以可以任务,buffer pool的大部分内存都被用来存放日志。剩下的那些主要留给应用日志时读取的数据页,因为目前来说日志应用是单线程的,读取一个日志,把所有日志应用完,然后就可以刷回磁盘了,不需要太多的内存。
应用日志的上层函数为recv_apply_hashed_log_recs
(应用日志也可能在io_helper函数中进行),主要作用就是遍历hash_table,从磁盘读取对每个数据页,依次应用哈希桶中的日志。应用完所有的日志后,如果需要则把buffer_pool的页面都刷盘,毕竟空间有限。有以下几点值得注意:
同一个数据页的日志必须按照lsn从小到大应用,否则数据会被覆盖。只应用redo日志lsn大于page_lsn的日志,只有这些日志需要重做,其余的忽略。应用完日志后,把脏页加入脏页列表,由于脏页列表是按照最老修改lsn(oldest_modification)来排序的,这里通过引入一颗红黑树来加速查找插入的位置,时间复杂度从之前的线性查找降为对数级别。
当需要某个数据页的时候,如果发现其没有在Buffer Pool中,则会查看这个数据页周围32个数据页,是否也需要做恢复,如果需要则可以一起读取出来,相当于做了一次io合并,减少io操作(recv_read_in_area
)。由于这个是异步读取,所以最终应用日志的活儿是由io_helper线程来做的(buf_page_io_complete
),此外,为了防止短时间发起太多的io,在代码中加了流量控制的逻辑(buf_read_recv_pages
)。如果发现某个数据页在内存中,则直接调用recv_recover_page
recv_apply_hashed_log_recs
입니다(응용 프로그램 로그는 io_helper 함수에서도 수행될 수 있습니다). hash_table을 탐색하고 디스크에서 읽습니다. 각 데이터 페이지를 가져오고 해시 버킷에 로그를 차례로 적용합니다. 모든 로그를 적용한 후 필요한 경우 모든 buffer_pool 페이지를 플러시합니다. 결국 공간이 제한됩니다. 다음 사항에 주목할 필요가 있습니다. 🎜recv_read_in_area
). 비동기 읽기이기 때문에 최종 애플리케이션 로그 작업은 io_helper 스레드(buf_page_io_complete
)에 의해 수행됩니다. 또한, 짧은 시간에 너무 많은 iOS가 시작되는 것을 방지하기 위해 트래픽이 추가됩니다. 코드에 대한 제어 논리(buf_read_recv_pages
). 데이터 페이지가 메모리에 있는 것으로 확인되면 recv_recover_page
애플리케이션 로그가 직접 호출됩니다. 이를 통해 InnoDB 애플리케이션 로그가 실제로 단일 스레드 애플리케이션 로그가 아니라는 것을 알 수 있습니다. 충돌 복구를 위한 기본 스레드 외에도 io_helper 스레드도 복구에 참여합니다. 동시 스레드 수는 io_helper의 읽기 스레드 수에 따라 다릅니다. 🎜🎜🎜🎜리두 롤포워드 데이터베이스가 실행된 후에는 데이터베이스의 모든 데이터 페이지가 이미 일관된 상태에 있으므로 안전하게 실행 취소 롤백 데이터베이스를 실행할 수 있습니다. 데이터베이스가 충돌하면 커밋되지 않은 트랜잭션이나 커밋된 트랜잭션이 있을 수 있습니다. 이때 커밋할지 여부를 결정해야 합니다. 크게 세 단계로 나누어진다. 먼저 언두 로그를 스캔하고 언두 로그 링크드 리스트를 재설정한 다음, 이전 단계에서 구축한 링크드 리스트를 기반으로 크래시 이전의 트랜잭션을 재구성, 즉 상태를 복원한다. 당시 거래 내역입니다. 마지막으로 트랜잭션의 상태에 따라 롤백이나 커밋이 수행됩니다. 🎜 in recv_recovery_from_checkpoint_start_func
之后,recv_recovery_from_checkpoint_finish
之前,调用了trx_sys_init_at_db_start
,这个函数做了上述三步中的前两步。
第一步在函数trx_rseg_array_init
中处理,遍历整个undo日志空间(最多TRX_SYS_N_RSEGS(128)个segment),如果发现某个undo segment非空,就进行初始化(trx_rseg_create_instance
)。整个每个undo segment,如果发现undo slot非空(最多TRX_RSEG_N_SLOTS(1024)个slot),也就行初始化(trx_undo_lists_init
)。在初始化undo slot后,就把不同类型的undo日志放到不同链表中(trx_undo_mem_create_at_db_start
)。undo日志主要分为两种:TRX_UNDO_INSERT和TRX_UNDO_UPDATE。前者主要是提供给insert操作用的,后者是给update和delete操作使用。之前说过,undo日志有两种作用,事务回滚时候用和MVCC快照读取时候用。由于insert的数据不需要提供给其他线程用,所以只要事务提交,就可以删除TRX_UNDO_INSERT类型的undo日志。TRX_UNDO_UPDATE在事务提交后还不能删除,需要保证没有快照使用它的时候,才能通过后台的purge线程清理。
第二步在函数trx_lists_init_at_db_start
中进行,由于第一步中,已经在内存中建立起了undo_insert_list和undo_update_list(链表每个undo segment独立),所以这一步只需要遍历所有链表,重建起事务的状态(trx_resurrect_insert
和trx_resurrect_update
)。简单的说,如果undo日志的状态是TRX_UNDO_ACTIVE,则事务的状态为TRX_ACTIVE,如果undo日志的状态是TRX_UNDO_PREPARED,则事务的状态为TRX_PREPARED。这里还要考虑变量srv_force_recovery的设置,如果这个变量值为非0,所有的事务都会回滚(即事务被设置为TRX_ACTIVE),即使事务的状态应该为TRX_STATE_PREPARED。重建起事务后,按照事务id加入到trx_sys->trx_list链表中。最后,在函数trx_sys_init_at_db_start
中,会统计所有需要回滚的事务(事务状态为TRX_ACTIVE)一共需要回滚多少行数据,输出到错误日志中,类似:5 transaction(s) which must be rolled back or cleaned up。InnoDB: in total 342232 row operations to undo的字样。
第三步的操作在两个地方被调用。一个是在recv_recovery_from_checkpoint_finish
的最后,另外一个是在recv_recovery_rollback_active
中。前者主要是回滚对数据字典的操作,也就是回滚DDL语句的操作,后者是回滚DML语句。前者是在数据库可提供服务之前必须完成,后者则可以在数据库提供服务(也即是崩溃恢复结束)之后继续进行(通过新开一个后台线程trx_rollback_or_clean_all_recovered
来处理)。因为InnoDB认为数据字典是最重要的,必须要回滚到一致的状态才行,而用户表的数据可以稍微慢一点,对外提供服务后,慢慢恢复即可。因此我们常常在会发现数据库已经启动起来了,然后错误日志中还在不断的打印回滚事务的信息。事务回滚的核心函数是trx_rollback_or_clean_recovered
,逻辑很简单,只需要遍历trx_sys->trx_list,按照事务不同的状态回滚或者提交即可(trx_rollback_resurrected
). 여기서 주목해야 할 점은 트랜잭션이 TRX_STATE_PREPARED 상태인 경우 InnoDB 계층에서 처리가 수행되지 않는다는 것입니다. binlog가 작성된 경우 서버 계층은 트랜잭션을 롤백할지 여부를 결정해야 합니다. , binlog가 기록되었으므로 트랜잭션이 대기 데이터베이스로 전송될 수 있습니다. 기본 데이터베이스가 롤백되면 binlog가 기록되지 않으면 트랜잭션이 일치하지 않게 됩니다. 롤백되었습니다.
innodb_fast_shutdown:
innodb_fast_shutdown = 0. 이는 MySQL이 종료되면 로그 플러시 및 데이터 페이지 플러시뿐만 아니라 데이터 정리(퍼지), ibuf 병합, 버퍼 풀 덤프 및 지연 테이블 삭제 작업(해당되는 경우)도 포함하는 느린 종료가 실행됨을 의미합니다. table has 완료되지 않은 작업이 있습니다. drop table을 실행하고 반환에 성공하더라도 테이블이 즉시 삭제되지 않을 수 있습니다.
innodb_fast_shutdown = 1. 이는 기본값으로, MySQL이 종료되면 로그와 데이터만 플러시된다는 의미입니다.
innodb_fast_shutdown = 2. 이는 마치 MySQL이 충돌한 것처럼 닫히면 로그만 플러시되고 다른 작업은 수행되지 않음을 의미합니다.
이 매개변수의 값이 클수록 MySQL은 더 빨리 종료되지만 시작 속도는 느려집니다. 이는 종료 중에 수행해야 하는 작업을 충돌 복구로 전환하는 것과 같습니다. 또한, MySQL을 업그레이드할 경우 첫 번째 방법을 사용하여 완전히 종료하는 것이 좋습니다.
innodb_force_recovery:
이 매개변수는 주로 InnoDB가 시작될 때 수행하는 작업을 제어하는 데 사용됩니다. 값이 클수록 수행되는 작업이 줄어들고 시작이 더 쉬워지지만 데이터 불일치의 위험도 커집니다. 통제할 수 없는 이유로 MySQL을 시작할 수 없는 경우 이 매개변수를 설정하고 MySQL이 시작될 때까지 1부터 점진적으로 증가시킨 다음 SELECT INTO OUTFILE을 사용하여 데이터를 내보내고 데이터 손실을 줄이기 위해 최선을 다할 수 있습니다.
innodb_force_recovery=0. 이는 기본 매개변수이며 시작 시 로그 적용 취소, 로그 롤백 취소, 백그라운드 마스터 시작 및 스레드 제거, ibuf 병합을 포함하여 모든 작업을 수행합니다. 데이터 페이지가 손상된 것으로 감지되었습니다. 시스템 테이블스페이스에 있으면 충돌이 발생합니다. 사용자 테이블스페이스에 있으면 오류 로그가 기록됩니다.
innodb_force_recovery = 1. 데이터 페이지가 손상된 것으로 감지되면 충돌이나 오류가 발생하지 않으며(buf_page_io_complete
), 시작 시 테이블스페이스의 첫 번째 데이터 페이지의 정확성이 확인되지 않습니다( >fil_check_first_page
), 테이블스페이스에 액세스할 수 없고 응급 복구가 계속되고(fil_open_single_table_tablespace
, fil_load_single_table_tablespace
), ddl 작업을 수행할 수 없습니다(check_if_supported_inplace_alter
code>), 동시에 데이터베이스는 쓰기 작업(row_insert_for_mysql
, row_update_for_mysql
등)을 수행할 수 없으며 준비된 모든 트랜잭션도 롤백됩니다. (trx_resurlect_insert
, trx_resurlect_update_in_prepared_state
). 이 옵션은 여전히 매우 일반적으로 사용됩니다. 디스크 불량으로 인해 데이터 페이지가 손상될 수 있습니다. 1로 설정하면 데이터베이스가 정상적으로 시작됩니다. buf_page_io_complete
),启动的时候也不会校验表空间第一个数据页的正确性(fil_check_first_page
),表空间无法访问也继续做崩溃恢复(fil_open_single_table_tablespace
、fil_load_single_table_tablespace
),ddl操作不能进行(check_if_supported_inplace_alter
),同时数据库也被不能进行写入操作(row_insert_for_mysql
、row_update_for_mysql
等),所有的prepare事务也会被回滚(trx_resurrect_insert
、trx_resurrect_update_in_prepared_state
)。这个选项还是很常用的,数据页可能是因为磁盘坏了而损坏了,设置为1,能保证数据库正常启动。
innodb_force_recovery = 2。除了设置1之后的操作不会运行,后台的master和purge线程就不会启动了(srv_master_thread
、srv_purge_coordinator_thread
等),当你发现数据库因为这两个线程的原因而无法启动时,可以设置。
innodb_force_recovery = 3。除了设置2之后的操作不会运行,undo回滚数据库也不会进行,但是回滚段依然会被扫描,undo链表也依然会被创建(trx_sys_init_at_db_start
)。srv_read_only_mode会被打开。
innodb_force_recovery = 4。除了设置3之后的操作不会运行,ibuf的操作也不会运行(ibuf_merge_or_delete_for_page
),表信息统计的线程也不会运行(因为一个坏的索引页会导致数据库崩溃)(info_low
、dict_stats_update
等)。从这个选项开始,之后的所有选项,都会损坏数据,慎重使用。
innodb_force_recovery = 5。除了设置4之后的操作不会运行,回滚段也不会被扫描(recv_recovery_rollback_active
),undo链表也不会被创建,这个主要用在undo日志被写坏的情况下。
innodb_force_recovery = 6。除了设置5之后的操作不会运行,数据库前滚操作也不会进行,包括解析和应用(recv_recovery_from_checkpoint_start_func
innodb_force_recovery=2. 1로 설정한 이후의 작업은 실행되지 않을 뿐만 아니라, 데이터베이스를 찾을 때 백그라운드 마스터 및 제거 스레드(srv_master_thread
, srv_purge_coordinator_thread
등)도 시작되지 않습니다. 이는 이 두 가지 때문입니다. 스레드 이유 때문에 스레드를 시작할 수 없는 경우에 설정할 수 있습니다.
trx_sys_init_at_db_start
) . srv_read_only_mode가 켜집니다. ibuf_merge_or_delete_for_page
). 테이블 정보 통계에 대한 스레드도 실행되지 않습니다(잘못된 인덱스 페이지로 인해 데이터베이스가 충돌)( info_low
, dict_stats_update
등). 이 옵션부터 시작하면 이후의 모든 옵션은 데이터를 손상시키므로 주의해서 사용하세요. recv_recovery_rollback_active
) undo 연결 목록이 생성되지 않습니다. 이는 주로 undo 로그를 작성할 때 사용됩니다. 나쁜. innodb_force_recovery=6. 5로 설정한 이후의 작업 외에 구문 분석 및 적용(recv_recovery_from_checkpoint_start_func
)을 포함한 데이터베이스 롤포워드 작업이 수행되지 않습니다.
위 내용은 MySQL 엔진 기능 및 InnoDB 충돌 복구에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!