程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> MySQL綜合教程 >> InnoDB源碼分析--緩沖池(二),innodb源碼--緩沖

InnoDB源碼分析--緩沖池(二),innodb源碼--緩沖

編輯:MySQL綜合教程

InnoDB源碼分析--緩沖池(二),innodb源碼--緩沖


     轉載請附原文鏈接:http://www.cnblogs.com/wingsless/p/5578727.html

    上一篇中我簡單的分析了一下InnoDB緩沖池LRU算法的相關源碼,其實說不上是分析,應該是自己的筆記,不過我還是發揚大言不慚的精神寫成分析好了。在此之後,我繼續閱讀了Buf0rea.c文件,因為這裡寫的就是如何將block讀取到內存中的函數。

    這個文件裡很顯眼的有這樣一個函數:buf_read_page,這是一個高層的函數,它的作用就是:reads a page asynchronously from a file to the buffer buf_pool if it is not already there。采用異步的方式將文件中的頁讀入buf_pool。大體上看一眼這個函數,發現它主要搞了以下幾個工作:

    1 隨機預讀(buf_read_ahead_random)。隨機預讀是一個可以提高效率的策略,它的主要思想是:給定的space和offset確定的那頁,可以計算出一個范圍,如果這個范圍內的頁(pages)有一部分已經被訪問(阈值:BUF_READ_AHEAD_RANDOM_THRESHOLD),那麼這個范圍內的頁(pages)就會被預讀。看一下函數內是怎麼寫的:

//確定一個邊界,邊界內的頁,都要進行條件判斷
low = (offset / BUF_READ_AHEAD_RANDOM_AREA) * BUF_READ_AHEAD_RANDOM_AREA; high = (offset / BUF_READ_AHEAD_RANDOM_AREA + 1) * BUF_READ_AHEAD_RANDOM_AREA; if (high > fil_space_get_size(space)) { high = fil_space_get_size(space); } 省略部分...
//對邊界內的頁進行條件判斷 for (i = low; i < high; i++) { block = buf_page_hash_get(space, i); if ((block) && (block->LRU_position > LRU_recent_limit) && block->accessed) { recent_blocks++; } } mutex_exit(&(buf_pool->mutex)); if (recent_blocks < BUF_READ_AHEAD_RANDOM_THRESHOLD) { /* Do nothing */ return(0); } 省略部分...
//如果之前的判斷都通過,則函數可以進行下面的步驟
//邊界范圍內的每一個頁都會被預讀:(buf_read_page_low),預讀采用異步的方式 for (i = low; i < high; i++) { /* It is only sensible to do read-ahead in the non-sync aio mode: hence FALSE as the first parameter */ if (!ibuf_bitmap_page(i)) { count += buf_read_page_low( &err, FALSE, ibuf_mode | OS_AIO_SIMULATED_WAKE_LATER, space, tablespace_version, i); if (err == DB_TABLESPACE_DELETED) { ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: Warning: in random" " readahead trying to access\n" "InnoDB: tablespace %lu page %lu,\n" "InnoDB: but the tablespace does not" " exist or is just being dropped.\n", (ulong) space, (ulong) i); } } }

     滿足條件的頁就會被預讀,注意預讀采用異步的方式,同時,(space,offset)指定的頁,也會被預讀。這裡是一個我有點搞不明白的地方,這個頁既然被異步預讀了,後面還會在同步的讀取一次,且聽後話。

     2 物理讀取。預讀結束之後,buf_read_page函數就會調度buf_read_page_low函數,進行數據的讀取,注意這個函數剛才預讀的時候也使用過,但是這次采用同步的方式,注釋寫的很明白:“ We do the i/o in the synchronous aio mode to save thread”。這個函數還會在給block->frame加x-lock鎖,這個操作會在函數調度下一級函數buf_page_init_for_read的時候進行,代碼:rw_lock_x_lock_gen(&(block->lock), BUF_IO_READ)。而buf_page_init_for_read函數的主要作用就是從LRU裡分配一個buf_block_t*,並對這個buf_block_t*加x-lock鎖。我主要看到了這幾行:

//分配一個buffer block
block = buf_block_alloc();
//向buffer pool中初始化一個page buf_page_init(space, offset, block);
//將block插入LRU鏈表中,只能插入old鏈表 buf_LRU_add_block(block, TRUE); /* TRUE == to old blocks */ rw_lock_x_lock_gen(&(block->lock), BUF_IO_READ);

    注意,buf_read_page_low函數中最後有這樣一段:

if (sync) {
        /* The i/o is already completed when we arrive from
        fil_read */
        buf_page_io_complete(block);
    }

     如果是同步方式,那麼就用buf_page_io_complete函數釋放所有的x-lock:rw_lock_x_unlock_gen(&(block->lock), BUF_IO_READ);

     3 物理讀取結束之後,會調度buf_flush_free_margin函數,在需要的情況下,flush掉LRU鏈表的尾部。

     

     總結一下上面的步驟,發現這是地地道道的物理讀取,即從磁盤中將數據讀取到內存中。在《MySQL內核--InnoDB存儲引擎》一書的12章裡還介紹了一種讀取方式叫做邏輯讀取,現在分析如下。

     從書中的描述裡看,我覺得這個叫做邏輯讀取有點不好理解。個人覺得這個邏輯讀取其實就是一個流程:

     

      基於我的理解畫的,可能有疏漏的地方。這裡就需要看這個函數:buf_page_get_gen,它的注釋也寫得很明白:This is the general function used to get access to a database page。提供了一個訪問數據庫頁的通用方法。

     這個函數有個很有意思的地方就是它的入參裡有很多的mode,這就給該函數帶來了許多種可能的返回。我無心看這些,但是有一個地方卻很吸引我,就是一個goto。學C的時候老師說,goto是C語言歷史上臭名昭著的一個關鍵字,大家初學,千萬別用。但是又有持不同意見的人認為善用goto能帶來意想不到的效果,我相信MySQL的作者們goto用的非常好。

     函數中有一個loop標記,也就是說函數是循環著讀取block的,直到滿足一些條件。首先會從緩沖池裡尋找:block = buf_page_hash_get(space, offset),沒有的話就會使用這個函數:buf_read_page(space, offset)將block讀入,然後goto到開始的地方,將block置為NULL,重新開始,這次就能從緩沖池裡找到block了。下面的代碼是很多的判斷,不過這裡很顯眼:

mutex_exit(&buf_pool->mutex);

    /* Check if this is the first access to the page*/    accessed = block->accessed;

    block->accessed = TRUE;

    mutex_exit(&block->mutex);

    buf_block_make_young(block);

省略部分...
if (!accessed) {
/* In the case of a first access, try to apply linear
read-ahead */

buf_read_ahead_linear(space, offset);
}

 

    這裡判斷了block是不是第一次被訪問,但是很奇怪,這個block立刻就被make young了,這和我以前的認知倒是不太一樣了,不過這篇淘寶丁奇的博文(https://yq.aliyun.com/articles/8827)裡寫到了這一點,可以參考一下,經過我的分析發現,make young也不是那麼笨的,它要判斷這個block是不是需要被make young(if (buf_block_peek_if_too_old(block)))。如果是第一次被訪問,就會觸發線性預讀函數的調用。

    終於說到了線性預讀。留著明天寫吧。

    看代碼果然過瘾啊。

    

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved