InnoDB 和大部分的存儲(chǔ)引擎一樣, 都是采用WAL 的方式進(jìn)行寫入數(shù)據(jù),所有的數(shù)據(jù)都先寫入到redo log, 然后后續(xù)再從buffer pool 刷臟到數(shù)據(jù)頁又或者是備份恢復(fù)的時(shí)候從redo log 恢復(fù)到buffer poll, 然后在刷臟到數(shù)據(jù)頁,WAL很重要的一點(diǎn)是將隨機(jī)寫轉(zhuǎn)換成了順序?qū)? 所以在機(jī)械磁盤時(shí)代,順序?qū)懙男阅苓h(yuǎn)遠(yuǎn)大于隨機(jī)寫的背景下, 充分利用了磁盤的性能. 但是也帶來一個(gè)問題, 就是任何的寫入操作都必須加鎖訪問, 保證上一個(gè)寫入操作完成以后, 才能進(jìn)行下一個(gè)寫入操作.在 InnoDB 早期版本也是這樣實(shí)現(xiàn), 但是隨著cpu 核數(shù)的增長(zhǎng),這樣頻繁的加鎖就無法發(fā)揮多核的性能, 所以在InnoDB 8.0 改成了無鎖實(shí)現(xiàn)這個(gè)是官方的介紹:https://mysqlserverteam.com/mysql-8-0-new-lock-free-scalable-wal-design
5.6 版本實(shí)現(xiàn)
有兩個(gè)操作需要獲得全局的mutex, log_sys_t::mutex, log_sys_t::flush_order_mutex
每一個(gè)用戶連接有一個(gè)線程, 要寫入數(shù)據(jù)之前必須先獲得log_sys_t::mutex,用來保證只有一個(gè)用戶線程在寫入log buffer那么隨著連接數(shù)的增加, 這個(gè)性能必然會(huì)受到影響
同樣的在把已經(jīng)寫入完成的redo log 加入到flush list 的時(shí)候,為了保證只有一個(gè)用戶線程從log buffer 上添加buffer 到flush list,因此需要去獲得log_sys_t::flush_order_mutex 來保證
如圖:
因此在5.6 版本的實(shí)現(xiàn)中, 我們需要先獲得log_sys_t::mutex, 然后寫入buffer,然后獲得log_sys_t::flush_order_mutex, 釋放log_sys_t::mutex, 然后把對(duì)應(yīng)的 page加入到flush list
所以8.0 無鎖實(shí)現(xiàn)主要就是要去掉這兩個(gè)mutex
8.0 無鎖實(shí)現(xiàn)
log_sys_t::mutex*
在去掉第一個(gè)log_sys_t::mutex 的時(shí)候, 通過在寫入之前先預(yù)先分配地址,然后在寫入的時(shí)候往指定地址寫入, 這樣就無需搶mutex.同樣, 問題來了:所有的線程都去獲得lsn 地址的時(shí)候, 同樣需要有一個(gè)mutex 來防止沖突, InnoDB通過使用atomic 來達(dá)到無鎖的實(shí)現(xiàn), 即: const sn_t start_sn = log.sn.fetch_add(len);
在每一個(gè)線程獲得了自己要寫入的lsn 的位置以后, 寫入自然就可以并發(fā)起來了.
那么在寫入的時(shí)候, 如果位置在前面的線程未寫完, 而位置靠后的已經(jīng)寫完了,這個(gè)時(shí)候我該如何將Log buffer 中的內(nèi)容寫入到redo log,肯定不允許寫入的數(shù)據(jù)有空洞.
8.0 里面引入了log_writer 線程, log_writer 線程去檢查log buffer 是否有空洞.具體實(shí)現(xiàn)是引入了叫 recent_written 用來記錄log buffer 是否連續(xù), 這個(gè)recent_written 是一個(gè)link_buf 實(shí)現(xiàn), 類型于并查集. 因此最大t允許并發(fā)寫入的大小就是這個(gè)recent_written 的大小
link_buf 實(shí)現(xiàn)如圖:
這個(gè)后臺(tái)線程在用戶寫入數(shù)據(jù)到recent_written buffer 的時(shí)候, 就被喚醒,檢查這個(gè)recent_written 連續(xù)的位置是否可以往前推進(jìn), 如果可以, 就往前走,將recent_written buffer 中的內(nèi)容寫入到redo log
log_sys_t::flush_order_mutex
如果不去掉flush_order_mutex, 用戶線程依然無法并發(fā)起來, 因?yàn)橛脩艟€程在寫完redolog 以后, 需要把對(duì)應(yīng)的page 加入到flush list才可以退出, 而加入到flush list需要去獲得 flush_order_mutex 鎖, 才能保證順序的加入flush list.因此也必須把flush_order_mutex 去掉.
具體做法允許把log buffer 中的對(duì)應(yīng)的臟頁無序的添加到flush list. 用戶寫完logbuffer 以后就可以把對(duì)應(yīng)的 log buffer 對(duì)應(yīng)的臟頁添加到flush list.而無需去搶flush_order_mutex. 這樣可能出現(xiàn)加入到flush list 上的page lsn 是無序的,因此在做checkpoint 的時(shí)候, 就無法保證每一個(gè)flush list 上面最頭的page lsn是最小的
InnoDB 用一個(gè)recent_closed 來記錄添加到flush list 的這一段log buffer 是否連續(xù),那么容易得出, flush list 上page lsn - recent_closed.size() 得到的lsn用于做checkpoint 肯定的安全的.
同樣, InnoDB 后臺(tái)有Log_closer 線程定期檢查recent_closed 是否連續(xù), 如果連續(xù)就把recent_closed buffer 向前推進(jìn), 那么checkpoint 的信息也可以往前推進(jìn)了
所以在8.0 的實(shí)現(xiàn)中, 把一個(gè)write redo log 的操作分成了幾個(gè)階段
獲得寫入位置, 實(shí)現(xiàn): 用戶線程
寫入數(shù)據(jù)到log buffer 實(shí)現(xiàn): 用戶線程
將log buffer 中的數(shù)據(jù)寫入到 redo log 文件 實(shí)現(xiàn): log writer
將redo log 中的page cache flush 到磁盤 實(shí)現(xiàn): log flusher
將redo log 中的log buffer 對(duì)應(yīng)的page 添加到flush list
更新可以打checkpoint 位點(diǎn)信息 recent_closed 實(shí)現(xiàn): log closer
根據(jù)recent_closed 打checkpoint 信息 實(shí)現(xiàn): log checkpointer
代碼實(shí)現(xiàn)
redo log 里面主要的內(nèi)存結(jié)構(gòu)
log file. 也就是我們常見的ib_logfile 文件
log buffer, 通常的大小是64M. 用戶在寫入的時(shí)候先從mtr 拷貝到redo log buffer, 然后在log buffer 里面會(huì)加入相應(yīng)的header/footer 信息, 然后由log buffer 刷到redo log file.
log recent written buffer 默認(rèn)大小是4M, 這個(gè)是MySQL 8.0 加入的, 為的是提高寫入時(shí)候的concurrent, 早5.6 版本的時(shí)候, 寫入Log buffer 的時(shí)候是需要獲得Lock, 然后順序的寫入到Log Buffer. 在8.0 的時(shí)候做了優(yōu)化, 寫入log buffer 的時(shí)候先reserve 空間, 然后后續(xù)的時(shí)候?qū)懭刖涂梢圆⑿械膶懭肓? 也就是這一段的內(nèi)容是允許有空洞的.
log recent closed buffer 默認(rèn)大小也是4M, 這個(gè)也是MySQL 8.0 加入的, 可以理解為log recent written buffer 在這個(gè)log buffer 的最前面, log recent closed buffer 在log buffer 的最后面. 也是為了添加到flush list 的時(shí)候提供concurrent. 具體實(shí)現(xiàn)方式和log recent written buffer 類似. 5.6 版本的時(shí)候, 將page 添加到flush list 的時(shí)候, 必須有一個(gè)Mutex 加鎖, 然后按照順序的添加到flush list 上. 8.0 的時(shí)候運(yùn)行recent closed buffer 大小的page 是并行的加入到flush list, 也就是這一段的內(nèi)容是允許有空洞的.
log write ahead buffer 默認(rèn)大小是 4k, 用于避免寫入小于4k 大小數(shù)據(jù)的時(shí)候需要先將磁盤上的讀取, 然后修改一部分的內(nèi)容, 在寫入回去.
主要的lsn
log.write_lsn
這個(gè)lsn 是到這個(gè)lsn 為止, 之前所有的data 已經(jīng)從log buffer 寫到log files了, 但是并沒有保證這些log file 已經(jīng)flush 到磁盤上了, 下面log.fushed_to_disk_lsn 指的才是已經(jīng)flush 到磁盤的lsn 了.
這個(gè)值是由log writer thread 來更新
log.buf_ready_for_write_lsn
這個(gè)lsn 主要是由于redo log 引入的concurrent writes 才引進(jìn)的, 也就是log recent written buffer. 也就是到了這個(gè)lsn 為止, 之前的log buffer 里面都不會(huì)有空洞,
這個(gè)值也是由 log writer thread 來更新
log.flushed_to_disk_lsn
到了這個(gè)lsn 為止, 所有的寫入到redo log 的數(shù)據(jù)已經(jīng)flush 到log files 上了
這個(gè)值是由log flusher thread 來更新
所以有 log.flushed_to_disk_lsn <= log.write_lsn <= log.buf_ready_for_write_lsn
log.sn
也就是不算上12字節(jié)的header, 4字節(jié)的checksum 以后的實(shí)際寫入的字節(jié)數(shù)信息. 通常用這個(gè)log.sn 去換算獲得當(dāng)前的current_lsn
*current_lsn = log_get_lsn(log);inline lsn_t log_get_lsn(const log_t &log) { return (log_translate_sn_to_lsn(log.sn.load()));}constexpr inline lsn_t log_translate_sn_to_lsn(lsn_t sn) { return (sn / LOG_BLOCK_DATA_SIZE * OS_FILE_LOG_BLOCK_SIZE + sn % LOG_BLOCK_DATA_SIZE + LOG_BLOCK_HDR_SIZE);}
以下幾個(gè)lsn 跟checkpoint 相關(guān)
log.buffer_dirty_pages_added_up_to_lsn
到這個(gè)lsn 為止, 所有的redo log 對(duì)應(yīng)的dirty page 已經(jīng)添加到buffer pool 的flush list 了.
這個(gè)值其實(shí)就是recent_closed.tail()
inline lsn_t log_buffer_dirty_pages_added_up_to_lsn(const log_t &log) { return (log.recent_closed.tail());}
這個(gè)值由log closer thread 來更新
log.available_for_checkpoint_lsn
到這個(gè)lsn 為止, 所有的redo log 對(duì)應(yīng)的dirty page 已經(jīng)flush 到btree 上了, 因此這里我們flush 的時(shí)候并不是順序的flush, 所以有可能存在有空洞的情況, 因此這個(gè)lsn 的位置并不是最大的redo log 已經(jīng)被flush 到btree 的位置. 而是可以作為checkpoint 的最大的位置.
這個(gè)值是由log checkpointer thread 來更新
log.last_checkpoint_lsn
到這個(gè)lsn 為止, 所有的btree dirty page 已經(jīng)flushed 到disk了, 并且這個(gè)lsn 值已經(jīng)被更新到了ib_logfile0 這個(gè)文件去了.
這個(gè)lsn 也是下一次recovery 的時(shí)候開始的地方, 因?yàn)閘ast_checkpoint_lsn 之前的redo log 已經(jīng)保證都flush 到btree 中去了. 所以比這個(gè)lsn 小的redo log 文件已經(jīng)可以刪除了, 因?yàn)閿?shù)據(jù)已經(jīng)都flush 到btree data page 中去了.
這個(gè)值是由log checkpointer thread 來更新
所以log.last_checkpoint_lsn <= log.available_for_checkpoint_lsn <= log.buf_dirty_pages_added_up_to_lsn
為什么會(huì)有這么多的lsn?
主要還是由于寫redo log 這個(gè)過程被拆開成了多個(gè)異步的流程.
先寫入到log buffer, 然后由log writer 異步寫入到 redo log, 然后再由log flusher 異步進(jìn)行刷新.
中間在log writer 寫入到 redo log 的時(shí)候, 引入了log recent written buffer 來提高concurrent 寫入性能.
同時(shí)在把這個(gè)page 加入到flush list 的時(shí)候, 也一樣是為了提高并發(fā), 增加了recent_closed buffer.
redo log 模塊后臺(tái)thread
在啟動(dòng)的函數(shù) Log_start_background_threads 的時(shí)候, 會(huì)把相應(yīng)的線程啟動(dòng)
os_thread_create(log_checkpointer_thread_key, log_checkpointer, &log); os_thread_create(log_closer_thread_key, log_closer, &log); os_thread_create(log_writer_thread_key, log_writer, &log); os_thread_create(log_flusher_thread_key, log_flusher, &log); os_thread_create(log_write_notifier_thread_key, log_write_notifier, &log); os_thread_create(log_flush_notifier_thread_key, log_flush_notifier, &log);
這里主要有
log_writer:
log_writer 這個(gè)線程等在writer_event 這個(gè)os_event上, 然后判斷的是 log.write_lsn.load() < ready_lsn. 這個(gè)ready_lsn 是去掃一下log buffer, 判斷是否有新的連續(xù)的內(nèi)存了. 這個(gè)線程主要做的事情就是不斷去檢查 log buffer 里面是否有連續(xù)的已經(jīng)寫入數(shù)據(jù)的內(nèi)存 buffer, ?執(zhí)行的函數(shù)是 log_writer_write_buffer()=>log_files_write_buffer()=>write_blocks()=>fil_redo_io() =>shard->do_redo_io()=>os_file_write() =>...=> pwrite(m_fh, m_buf, m_n, m_offset);
這里這個(gè)io 是同步, 非direct IO.
將這部分的數(shù)據(jù)內(nèi)容刷到redolog 中去, 但是不執(zhí)行fsync 命令, 具體執(zhí)行fsync 命令的是log_flusher.
問題: 誰來喚醒Log_writer 這個(gè)線程?
正常情況下. srv_flush_log_at_trx_commit == 1 的時(shí)候是沒有人去喚醒這個(gè)log_writer, 這個(gè)os_event_wait_for 是在pthread_cond_timedwait 上的, 這個(gè)時(shí)間為 srv_log_writer_timeout = 10 微秒.
這個(gè)線程被喚醒以后, 執(zhí)行l(wèi)og_writer_write_buffer() 后, 在執(zhí)行Log_files_write_buffer() 函數(shù)里面 執(zhí)行 notify_about_advanced_write_lsn() 函數(shù)去喚醒write_notifier_event,
同時(shí), 在執(zhí)行完成 log_writer_write_buffer() 后. 會(huì)判斷srv_flush_log_at_trx_commit == 1 就去喚醒 log.flusher_event
log_write_notifier:
log_write_notifer 是等待在 write_notifier_event 這個(gè)os_event上, 然后判斷的是 log.write_lsn.load() >= lsn, lsn 是上一次的log.write_lsn. 也就是判斷Log.write_lsn 有沒有增加, 如果有增加就喚醒這個(gè)log_write_notifier, 然后log_write_notifier 就去喚醒那些等待在 log.write_events[slot] 的用戶thread.
從上面可以看到, 由log_writer 執(zhí)行os_event_set 喚醒
有哪些線程等待在log.write_events上呢?
都是用戶的thread 最后會(huì)等待在Log.write_events上, 用戶的線程調(diào)用log_write_up_to, 最后根據(jù)
srv_flush_log_at_trx_commit 這個(gè)變量來判斷是執(zhí)行
!=1 log_wait_for_write(log, end_lsn); 然后等待在log.write_events[slot] 上.
const auto wait_stats = os_event_wait_for(log.write_events[slot], max_spins, srv_log_wait_for_write_timeout, stop_condition);
=1 log_wait_for_flush(log, end_lsn); 等待在log.flush_events[slot] 上.
const auto wait_stats = os_event_wait_for(log.flush_events[slot], max_spins, srv_log_wait_for_flush_timeout, stop_condition);
log_flusher
log_flusher 是等待在 log.flusher_event 上,
從上面可以看到一般來說, 由log_writer 執(zhí)行os_event_set 喚醒
如果是 srv_flush_log_at_trx_commit == 1 的場(chǎng)景, 也就是我們最常見的寫了事務(wù), 必須flush 到磁盤, 才能返回的場(chǎng)景. 然后判斷的是 last_flush_lsn < log.write_lsn.load(), 也就是上一次last_flush_lsn 比當(dāng)前的write_lsn, 如果比他小, 說明有新數(shù)據(jù)寫入了, 那么就可以執(zhí)行flush 操作了,
如果是 srv_flush_log_at_trx_commit != 1 的場(chǎng)景, 也就是寫了事務(wù)不需要保證redolog 刷盤的場(chǎng)景, 那么執(zhí)行的是
os_event_wait_time_low(log.flusher_event, flush_every_us - time_elapsed_us, 0);
也就是會(huì)定期的根據(jù)時(shí)間來喚醒, 然后執(zhí)行 flusher 操作.
最后 執(zhí)行完成flush 以后喚醒的是log.flush_notifier_event os_event_set(log.flush_notifier_event);
log_flush_notifier
和log_write_notifier 基本一樣, 等待在 flush_notifier_event 上, 然后判斷的是 log.flushed_to_disk_lsn.load() >= lsn, 這里lsn 是上一次的flushed_to_disk_lsn, 也就是判斷flushed_to_disk_lsn 有沒有增加, 如果有增加就喚醒等待在 flush_events[slot] 上面的用戶線程, 跟上面一樣, 也是用戶線程最后會(huì)等待在flush_events 上
從上面可以看到, 有l(wèi)og_flusher 喚醒它
log_closer
log_closer 這個(gè)線程是在后臺(tái)不斷的去清理recent_closed 的線程, 在mtr/mtr0mtr.cc:execute() 也就是mtr commit 的時(shí)候, 會(huì)把這個(gè)mtr 修改的內(nèi)容對(duì)應(yīng)start_lsn, end_lsn 的內(nèi)容添加到recent_closed buffer 里面, 并且在添加到recent_closed buffer 之前, 也會(huì)把相應(yīng)的page 都掛到buffer pool 的flush list 里面.
和其他線程不一樣的地方在于, Log_closer 并沒有wait 在一個(gè)條件變量上, 只是每隔1s 的輪詢而已.
而在這1s 一次的輪詢里面, 一直執(zhí)行的操作是 log_advance_dirty_pages_added_up_to_lsn() 這個(gè)函數(shù)類似recent_writtern 里面的 log_advance_ready_for_write_lsn(), 去這個(gè)recent_close 里面的Link_buf 里面
/* * 從recent_closed.m_tail 一直往下找, 只要有連續(xù)的就串到一起, 直到 * 找到有空洞的為止 * 只要找到數(shù)據(jù), 就更新m_tail 到最新的位置, 然后返回true * 一條數(shù)據(jù)都沒有返回false * 注意: 在advance_tail_until 操作里面, 本身同時(shí)會(huì)進(jìn)行的操作就是回收之前的空間 * 所以執(zhí)行完advance_tail_until 以后, 連續(xù)的內(nèi)存就會(huì)被釋放出來了 * 下面還有validate_no_links 函數(shù)進(jìn)行檢查是否釋放正確 */
這樣一直清理著recent_closed buffer, 就可以保證recent_closed buffer 一直是有空間的
log_closer thread 會(huì)一直更新著這個(gè) log_advance_dirty_pages_added_up_to_lsn(), 這個(gè)函數(shù)里面就是一直去更新recent_close buffer 里面的 log_buffer_dirty_pages_added_up_to_lsn(), 然后在做check pointer 的時(shí)候, 會(huì)一直去檢查這個(gè)log_buffer_dirty_pages_added_up_to_lsn(), 可以做check point 的lsn 必須小于這個(gè)log_buffer_dirty_pages_added_up_to_lsn(), 因?yàn)?log_buffer_dirty_pages_added_up_to_lsn 表示的是 recent close buffer 里面的其實(shí)位置, 在這個(gè)位置之前的Lsn 都已經(jīng)被填滿, 是連續(xù)的了, 在這個(gè)位置之后的lsn 沒有這個(gè)保證.
那么是誰負(fù)責(zé)更新recent_closed 這個(gè)數(shù)組呢?log_closed thread
什么時(shí)候把dirty page 加入到buffer pool 的 flush list 上?
在mtr->commit() 的時(shí)候, 就會(huì)把這個(gè)mtr 修改過的page 都加到flush list 上, 在添加到flush list 上之前, 我們會(huì)保證寫入到redo log, 并且這個(gè)redo log 已經(jīng)flush 了.
log_checkpointer
這個(gè)線程等待在 log.checkpointer_event 上, 然后判斷的是10*1000, 也就是10s 的時(shí)間,
os_event_wait_time_low(log.checkpointer_event, 10 * 1000, sig_count);
os_event_wait_time_low 是等待checkpointer_event 被喚醒, 或者超時(shí)時(shí)間10s 到了, 其實(shí)就是pthread_cond_timedwait()
正常情況下都是等10s 然后log_checkpointer 被喚醒, 那么被通知到checkpointer_event 被喚醒的場(chǎng)景在哪里呢?
其實(shí)也是在 log_writer_write_buffer() 函數(shù)里面, 先判斷
while(1) {const lsn_t lsn_diff = min_next_lsn - checkpoint_lsn;if (lsn_diff <= log.lsn_capacity) {? ?checkpoint_limited_lsn = checkpoint_lsn + log.lsn_capacity;? ?break;?}?log_request_checkpoint(log, false);? ?...?}?// 為什么需要在log_writer 的過程加入這個(gè)邏輯, 這個(gè)邏輯是判斷l(xiāng)sn_diff(當(dāng)前這次要寫入的數(shù)據(jù)的大小) 是否超過了log.lsn_capacity(redolog 的剩余容量大小), 如果比它小, 那么就可以直接進(jìn)行寫入操作, 就break 出去, 如果比它大, 那么說明如果這次寫入寫下去的話, 因?yàn)閞edolog 是rotate 形式的, 會(huì)把當(dāng)前的redolog 給寫壞, 所以必須先進(jìn)行一次checkpoint, 把一部分的redolog 中的內(nèi)容flush 到btree data中, 然后把這個(gè)checkpoint 點(diǎn)增加, 騰出空間.?// 所以我們看到如果checkpoint 做的不夠及時(shí), 會(huì)導(dǎo)致redolog 空間不夠, 然后直接影響到線上的寫入線程.?
首先我們必須知道一個(gè)問題是, 一次transaction 修改的page 什么時(shí)候flush 下去, 我們是不知道的. 因?yàn)橛脩糁恍枰獙懭氲絩edo log, 并且確認(rèn)redo log 已經(jīng)flush 了以后, 就直接返回了. 至于什么時(shí)候從Buffer pool flush 到btree data, 這個(gè)是后臺(tái)異步的, 用戶也不關(guān)注的. 但是我們打checkpoint 以后, 在checkpoint 之前的redo log 應(yīng)該是都可以刪除的, 因此我們必須保證打的checkpoint lsn 的這個(gè)點(diǎn)之前的redo log 已經(jīng)將對(duì)應(yīng)的page flush到磁盤上了,
那么這里的問題就是如何確定這個(gè)checkpoint lsn 點(diǎn)?
在函數(shù) log_update_available_for_checkpoint_lsn(log); 里面更新 log.available_for_checkpoint_lsn
具體的更新過程:
然后在log_request_checkpoint里面執(zhí)行 log_update_available_for_checkpoint_lsn(log) =>
const lsn_t oldest_lsn = log_get_available_for_checkpoint_lsn(log);
然后執(zhí)行 lsn_t lwn_lsn = buf_pool_get_oldest_modification_lwm() =>
buf_pool_get_oldest_modification_approx()
這里buf_pool_get_oldest_modification_approx() 指的是獲得大概的最老的lsn 的位置, 這里是引入了recent_closed buffer 帶來的一個(gè)問題, 因?yàn)橐肓?recent_closed buffer 以后, 從redo log 上面的page 添加到buffer pool 的flush list 是不能保證有序的, 有可能一個(gè)flush list 上面存在的是 98 => 85 => 110 這樣的情況. 因此這個(gè)函數(shù)只能獲得大概的oldest_modification lsn
具體的做法就是遍歷所有的buffer pool 的flush list, 然后只需要取出flush list 里面的最后一個(gè)元素(雖然因?yàn)橐肓藃ecent_closed 不能保證是最老的 lsn), 也就是最老的lsn, 然后對(duì)比8個(gè)flush_list, 最老的lsn 就是目前大概的lsn 了
然后在buf_pool_get_oldest_modification_lwm() 還是里面, 會(huì)將buf_pool_get_oldest_modification_approx() 獲得的 lsn 減去recent_closed buffer 的大小, 這樣得到的lsn 可以確保是可以打checkpoint 的, 但是這個(gè)lsn 不能保證是最大的可以打checkpoint 的lsn. 而且這個(gè) lsn 不一定是指向一個(gè)記錄的開始, 更多的時(shí)候是指向一個(gè)記錄的中間, 因?yàn)檫@里會(huì)強(qiáng)行減去一個(gè) recent_closed buffer 的size. 而以前在5.6 版本是能夠保證這個(gè)lsn 是默認(rèn)一個(gè)redo log 的record 的開始位置
最后通過 log_consider_checkpoint(log); 來確定這次是否要寫這個(gè)checkpointer 信息
然后在 log_should_checkpoint() 具體的有3個(gè)條件來判斷是否要做 checkpointer
最后決定要做的時(shí)候通過 log_checkpoint(log); 來寫入checkpointer 的信息
在log_checkpoint() 函數(shù)里面
通過 log_determine_checkpoint_lsn() 來判斷這次checkpointer 是要寫入dict_lsn, 還是要寫入available_for_checkpoint_lsn. 在 dict_lsn 指的是上一次DDL 相關(guān)的操作, 到dict_lsn 為止所有的metadata 相關(guān)的都已經(jīng)寫入到磁盤了, 這里為什么要把DDL 相關(guān)的操作和非 DDL 相關(guān)的操作分開呢?
最后通過 log_files_write_checkpoint 把checkpoint 信息寫入到ib_logfile0 文件中
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4333瀏覽量
62696 -
代碼
+關(guān)注
關(guān)注
30文章
4791瀏覽量
68694 -
MySQL
+關(guān)注
關(guān)注
1文章
816瀏覽量
26610
原文標(biāo)題:[InnoDB 源碼介紹] lock-free redo log in mysql8.0
文章出處:【微信號(hào):inf_storage,微信公眾號(hào):數(shù)據(jù)庫和存儲(chǔ)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論