InnoDB儲存引擎——Master Thread工作方式

readyao發表於2017-03-12

Master Thread是InnoDB儲存引擎非常核心的一個後臺執行緒,主要負責將緩衝池中的資料非同步重新整理到磁碟,保證資料的一致性,包括髒頁的重新整理、合併插入緩衝、UNDO頁的回收等。

InnoDB 1.0.x版本之前的Master Thread

Master Thread具有最高的執行緒優先順序別。內部由多個迴圈組成:主迴圈(loop)、後臺迴圈(backgroup loop)、重新整理迴圈(flush loop)、暫停迴圈(suspend loop)。Master Thread會根據資料庫執行的狀態在loop、backgroup loop、flush loop和suspend loop中進行切換。

loop是主迴圈,大多數的操作都在這個迴圈中,主要有兩大部分的操作——每秒鐘的操作和每10秒鐘的操作。虛擬碼如下:

void master_thread()
{
    loop:
    for(int i = 0; i < 10; ++i){
        do thing once per second;
        sleep 1 second if necessary;
    }
    do things once per ten seconds;
    goto loop;
}

每秒一次的操作包括:
1)日誌緩衝重新整理到磁碟,即使這個事務還沒有提交(總是);
2)合併插入緩衝(可能);
3)至多重新整理100個InnoDB的緩衝池中的髒頁到磁碟(可能);
4)如果當前沒有使用者活動,則切換到background loop(可能);

即使某個事務還沒有提交,InnoDB儲存引擎仍然每秒會將重做日誌緩衝中的內容重新整理到重做日誌檔案。這也解釋了為什麼再大的事務提交的時間也是很短的。

合併插入緩衝並不是每秒都會發生的。InnoDB儲存引擎會判斷當前一秒內發生的IO次數是否小於5次,如果小於5次,InnoDB儲存引擎認為當前的IO壓力很小,可以執行合併插入緩衝的操作;

重新整理100個髒頁也不是每秒都會發生的,InnoDB儲存引擎通過判斷當前緩衝池中髒頁的比例(buf_get_modified_ratio_pct)是否超過了配置檔案中
innodb_max_dirty_pages_pct這個引數(預設是90,代表90%),如果超過了這個值,InnoDB儲存引擎則認為需要做磁碟同步的操作,將100個髒頁寫入磁碟中。

綜上所述,虛擬碼可以進一步具體化。

void master_thread()
{
    loop:
    for(int i = 0; i < 10; ++i){
        thread_sleep(1);
        do log buffer flush to disk;
        if(last_one_second_ios < 5)
            do merge at most 5 insert buffer;
        if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
            do buffer pool flush 100 dirty page;
        if(no user activity)
            goto backgroud loop;
    }
    do things once per ten seconds;
    backgroud loop;
    do something;
    goto loop;
}

每10秒的操作主要是下面幾個方面:
1)重新整理100個髒頁到磁碟(可能)
2)合併至多5個插入緩衝(總是)
3)將日誌緩衝重新整理到磁碟(總是)
4)刪除無用的Undo頁(總是)
5)重新整理100個或者10個髒頁到磁碟(總是)

在以上過程中,InnoDB儲存引擎會先判斷過去10秒之內磁碟的IO操作是否小於200次,如果是,InnoDB儲存引擎認為當前有足夠的磁碟IO能力,因此將100個髒頁重新整理到磁碟。

接著,InnoDB儲存引擎會合並插入緩衝,每10秒都會發生。

之後,InnoDB儲存引擎會再進行一次將日誌緩衝重新整理到磁碟的操作,這和每秒一次時發生的操作是一樣的。

然後,InnoDB儲存引擎會執行full purge操作,即刪除無用的Undo頁。對錶進行update,delete這類的操作時,原先的行被標記為刪除,但是因為一致性讀的關係,需要保留這些行版本的資訊。但是在full purge過程中,InnoDB儲存引擎會判斷當前事務系統中已被刪除的行是否可以刪除,比如有時候可能還有查詢操作需要讀取之前版本的undo資訊,如果可以刪除,InnoDB儲存引擎會立即將其刪除。從原始碼中可以看出,InnoDB儲存引擎在執行full purge 操作時,每次最多嘗試回收20個undo頁。

然後,InnoDB儲存引擎會判斷緩衝池中髒頁的比例(buf_get_modified_ratio_pct),如果有超過70%的髒頁,則重新整理100個髒頁到磁碟,如果髒頁的比例小於70%,則只需重新整理10%的髒頁到磁碟。

虛擬碼進一步細化:

void master_thread()
{
    loop:
    for(int i = 0; i < 10; ++i){
        thread_sleep(1);
        do log buffer flush to disk;
        if(last_one_second_ios < 5)
            do merge at most 5 insert buffer;
        if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
            do buffer pool flush 100 dirty page;
        if(no user activity)
            goto backgroud loop;
    }
    if(last_ten_second_ios < 200)
        do buffer pool flush 100 dirty page;

    do merge at most 5 insert buffer;
    do log buffer flush to disk;
    do full purge;
    if(buf_get_modified_ratio_pct > 70%)
        do buffer pool flush 100 dirty page;
    else
        buffer pool flush 10 dirty page;
    goto loop;
    backgroud loop;
    do something;
    goto loop;
}

如果當前沒有使用者活動(資料庫空閒)或者資料庫關係,就會切換到backgroud loop這個迴圈。
backgroud loop會執行以下操作:
1)刪除無用的Undo頁(總是)
2)合併20個插入緩衝(總是)
3)跳回到主迴圈(總是)
4)不斷重新整理100個頁直到符合條件(可能,需要跳轉到flush loop中完成)

如果flush loop中也沒有什麼事情可以做了,InnoDB儲存引擎會切換到suspend_loop,將Master Thread掛起,等待事件的發生。若使用者啟用了InnoDB儲存引擎,卻沒有使用任何InnoDB儲存引擎的表,那麼Master Thread總是處於掛起的狀態。

最後,Master Thread完整的虛擬碼如下:

void master_thread()
{
    loop:
    for(int i = 0; i < 10; ++i){
        thread_sleep(1);
        do log buffer flush to disk;
        if(last_one_second_ios < 5)
            do merge at most 5 insert buffer;
        if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
            do buffer pool flush 100 dirty page;
        if(no user activity)
            goto backgroud loop;
    }
    if(last_ten_second_ios < 200)
        do buffer pool flush 100 dirty page;

    do merge at most 5 insert buffer;
    do log buffer flush to disk;
    do full purge;
    if(buf_get_modified_ratio_pct > 70%)
        do buffer pool flush 100 dirty page;
    else
        buffer pool flush 10 dirty page;

    backgroud loopdo full purge
    do merge 20 insert buffer;
    if not idle
        goto loop:
    else
        goto flush loop

    flush loop:
    do buffer pool flush 100 dirty page;
    if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
        goto flush loop;

    goto suspend loop;

    suspend loop:
    suspend_thread();
    waiting event;
    goto loop;
}

InnoDB 1.2.x版本之前的Master Thread

1.0.x版本中,InnoDB儲存引擎最多隻會重新整理100個髒頁到磁碟,合併20個插入緩衝。如果是在寫入密集的應用程式中,每秒可能會產生大於100個的髒頁,如果是產生大於20個插入緩衝的情況,那麼可能會來不及重新整理所有的髒頁以及合併插入緩衝。

後來,InnoDB儲存引擎提供了引數innodb_io_capacity,用來表示磁碟IO的吞吐量,預設值為200。

mysql> show variables like 'innodb_io_capacity';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| innodb_io_capacity | 200   |
+--------------------+-------+
1 row in set (0.00 sec)

對於重新整理到磁碟的頁的數量,會按照innodb_io_capacity的百分比來進行控制。規則如下:
1)在合併插入緩衝時,合併插入緩衝的數量為innodb_io_capacity值的5%;
2)在從緩衝區重新整理髒頁時,重新整理髒頁的數量為innodb_io_capacity;

如果使用者使用的是SSD類的磁碟,可以將innodb_io_capacity的值調高,直到符合磁碟IO的吞吐量為止;

另一個問題是引數innodb_max_dirty_pages_pct的預設值,在1.0.x版本之前,該值的預設值是90,意味著髒頁佔緩衝池的90%。InnoDB儲存引擎在每秒重新整理緩衝池和flush loop時會判斷這個值,如果該值大於innodb_max_dirty_pages_pct,才會重新整理100個髒頁,如果有很大的記憶體,或者資料庫伺服器的壓力很大,這時重新整理髒頁的速度反而會降低。
後來將innodb_max_dirty_pages_pct的預設值改為了75。這樣既可以加快重新整理髒頁的頻率,又能夠保證磁碟IO的負載。

mysql> show variables like 'innodb_max_dirty_pages_pct';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_max_dirty_pages_pct | 75    |
+----------------------------+-------+
1 row in set (0.00 sec)

還有一個新的引數是innodb_adaptive_flushing(自適應地重新整理),該值影響每秒重新整理髒頁的數量。原來的重新整理規則是:髒頁在緩衝池所佔的比例小於innodb_max_dirty_pages_pct時,不重新整理髒頁;大於innodb_max_dirty_pages_pct時,重新整理100個髒頁。隨著innodb_adaptive_flushing引數的引入,InnoDB通過一個名為buf_flush_get_desired_flush_rate的函式來判斷需要重新整理髒頁最合適的數量。buf_flush_get_desired_flush_rate函式通過判斷產生重做日誌的速率來決定最合適的重新整理髒頁數量。

之前每次進行full purge 操作時,最多回收20個Undo頁,從InnoDB 1.0.x版本開始引入了引數innodb_purge_batch_size,該引數可以控制每次full purge回收的Undo頁的數量。該引數的預設值為20,並可以動態地對其進行修改。

mysql> show variables like 'innodb_purge_batch_size';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| innodb_purge_batch_size | 20    |
+-------------------------+-------+
1 row in set (0.00 sec)

Master Thread的虛擬碼變為了下面的形式:

void master_thread()
{
    loop:
    for(int i = 0; i < 10; ++i){
        thread_sleep(1);
        do log buffer flush to disk;
        if(last_one_second_ios < 5%innodb_io_capacity)
            do merge 5%innodb_io_capacity insert buffer;
        if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
            do buffer pool flush 100%innodb_io_capacity dirty page;
        else if enable adaptive flush
            do buffer pool flush desired amount dirty page;
        if(no user activity)
            goto backgroud loop;
    }
    if(last_ten_second_ios < innodb_io_capacity)
        do buffer pool flush 100%innodb_io_capacity dirty page;

    do merge 5%innodb_io_capacity insert buffer;
    do log buffer flush to disk;
    do full purge;
    if(buf_get_modified_ratio_pct > 70%)
        do buffer pool flush 100%innodb_io_capacity dirty page;
    else
        do buffer pool flush 10%innodb_io_capacity dirty page;

    goto loop;
    backgroud loopdo full purge
    do merge 100%innodb_io_capacity insert buffer;
    if not idle
        goto loop:
    else
        goto flush loop

    flush loop:
    do buffer pool flush 100%innodb_io_capacity dirty page;
    if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
        goto flush loop;

    goto suspend loop;

    suspend loop:
    suspend_thread();
    waiting event;
    goto loop;
}

這個版本的效能得到了提高。

mysql> show engine innodb status\G
*************************** 1. row ***************************
  Type: InnoDB
  Name: 
Status: 
=====================================
170312 20:14:04 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 38 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 1 1_second, 1 sleeps, 0 10_second, 1 background, 1 flush
srv_master_thread log flush and writes: 1

可以看到主迴圈進行了1次,每秒的操作進行了1次,10秒一次的操作進行了0次,backgound loop進行了6次,flush loop進行了6次。

InnoDB 1.2.x版本的Master Thread

1.2.x版本中再次對Master Thread進行了優化。
Master Thread的虛擬碼如下:

if InnoDB is idle
    srv_master_do_idle_tasks();
else
    srv_master_do_active_tasks();

其中srv_master_do_idle_tasks()就是之前版本中每10秒的操作,srv_master_do_active_tasks()處理的是之前每秒中的操作。同時,對於重新整理髒頁的操作,從Master Thread執行緒分離到一個單獨的Page Cleaner Thread,從而減輕了Master Thread的工作,同時進一步提高了系統的併發性。

相關文章