7.4.6.3 中點插入策略
默認(rèn)地,MySQL 4.1的索引緩存管理系統(tǒng)采用LRU策略來選擇要被清除的緩存區(qū)塊,不過它也支持更完善的方法,叫做"中點插入策略"。
使用中點插入策略時,LRU鏈就被分割成兩半:一個熱子鏈,一個溫子鏈。兩半分割的點不是固定的,不過緩存管理系統(tǒng)會注意不讓溫子鏈部分"太短",總是至少包括全部緩存區(qū)塊的 key_cache_division_limit
比率。key_cache_division_limit
是緩存結(jié)構(gòu)體變量的組件部分,因此它是每個緩存都可以設(shè)置這個參數(shù)值。
當(dāng)一個索引區(qū)塊從表中讀入緩存時,它首先放在溫子鏈的末尾。當(dāng)達(dá)到一定的點擊率(訪問這個區(qū)塊)后,它就提升到熱子鏈中去。目前,要提升一個區(qū)塊的點擊率(3)對每個區(qū)塊來說都是一樣的。將來,我們會讓點擊率依靠B樹中對應(yīng)的索引區(qū)塊節(jié)點的級別:包含非葉子節(jié)點的索引區(qū)塊所要求的提升點擊率就低一點,包含葉子節(jié)點的B索引樹的區(qū)塊的值就高點。
提升起來的區(qū)塊首先放在熱子鏈的末尾。這個區(qū)塊在熱子鏈內(nèi)一直循環(huán)。如果這個區(qū)塊在該子鏈開頭位置停留時間足夠長了,它就會被降級回溫子鏈。這個時間是由索引緩存結(jié)構(gòu)體變量的組件 key_cache_age_threshold
值來決定的。
這個閥值是這么描述的,一個索引緩存包含了 N 個區(qū)塊,熱子鏈開頭的區(qū)塊在低于 N*key_cache_age_threshold/100
次訪問后就被移動到溫子鏈的開頭位置。它又首先成為被刪除的候選對象,因為要被替換的區(qū)塊還是從溫子鏈的開頭位置開始的。
中點插入策略就能在緩存中總能保持更有價值的區(qū)塊。如果更喜歡采用LRU策略,只需讓 key_cache_division_limit
的值低于默認(rèn)值 100。
中點插入策略能幫助改善在執(zhí)行需要有效掃描索引,它會將所有對應(yīng)到B樹中高級別的有價值的節(jié)點推出的查詢時的性能。為了避免這樣,就必須設(shè)定 key_cache_division_limit
遠(yuǎn)遠(yuǎn)低于100以采用中點插入策略。則在掃描索引操作時那些有價值的頻繁點擊的節(jié)點就會保留在熱子鏈中了。
7.4.6.4 索引預(yù)載入
如果索引緩存中有足夠的區(qū)塊用來保存全部索引,或者至少足夠保存全部非葉子節(jié)點,那么在使用前就載入索引緩存就很有意義了。將索引區(qū)塊以十分有效的方法預(yù)載入索引緩存緩沖:從磁盤中順序地讀取索引區(qū)塊。
沒有預(yù)載入,查詢所需的索引區(qū)塊仍然需要被放到緩存中去。雖然索引區(qū)塊要保留在緩存中,因為有足夠的緩沖,它們可以從磁盤中隨機讀取到,而非順序地。
想要預(yù)載入緩存,可以使用 LOAD INDEX INTO CACHE
語句。如下語句預(yù)載入了表 t1
和 t2
的索引節(jié)點(區(qū)塊):
mysql> LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES; +---------+--------------+----------+----------+ | Table | Op | Msg_type | Msg_text | +---------+--------------+----------+----------+ | test.t1 | preload_keys | status | OK | | test.t2 | preload_keys | status | OK | +---------+--------------+----------+----------+
增加修飾語 IGNORE LEAVES
就只預(yù)載入非葉子節(jié)點的索引區(qū)塊。因此,上述語句加載了 t1
的全部索引區(qū)塊,但是只加載 t2
的非葉子節(jié)點區(qū)塊。
如果使用 CACHE INDEX
語句將索引指向一個索引緩存,將索引區(qū)塊預(yù)先放到那個緩存中去。否則,索引區(qū)塊只會加載到默認(rèn)的緩存中去。
7.4.6.5 索引緩存大小
MySQL 4.1引進(jìn)了對每個索引緩存的新變量 key_cache_block_size
。這個變量可以指定每個索引緩存的區(qū)塊大小。用它就可以來調(diào)整索引文件I/O操作的性能。
當(dāng)讀緩沖的大小和本地操作系統(tǒng)的I/O緩沖大小一樣時,就達(dá)到了I/O操作的最高性能了。但是設(shè)置索引節(jié)點的大小和I/O緩沖大小一樣未必能達(dá)到最好的總體性能。讀比較大的葉子節(jié)點時,服務(wù)器會讀進(jìn)來很多不必要的數(shù)據(jù),這大大阻礙了讀其他葉子節(jié)點。
目前,還不能控制數(shù)據(jù)表的索引區(qū)塊大小。這個大小在服務(wù)器創(chuàng)建索引文件 `.MYI' 時已經(jīng)設(shè)定好了,它根據(jù)數(shù)據(jù)表的索引大小的定義而定。在很多時候,它設(shè)置成和I/O緩沖大小一樣。在將來,可以改變它的值,并且會全面采用變量 key_cache_block_size
。
7.4.6.6 重建索引緩存
索引緩存可以通過修改其參數(shù)值在任何時候重建它,例如:
mysql> SET GLOBAL cold_cache.key_buffer_size=4*1024*1024;
如果設(shè)定索引緩存的結(jié)構(gòu)體變量組件變量 key_buffer_size
或 key_cache_block_size
任何一個的值和它當(dāng)前的值不一樣,服務(wù)器就會清空原來的緩存,在新的變量值基礎(chǔ)上重建緩存。如果緩存中有任何的'臟'索引塊,服務(wù)器會先把它們保存起來然后才重建緩存。重新設(shè)定其他的索引緩存變量并不會重建緩存。
重建緩存時,服務(wù)器會把所有的'臟'緩沖的內(nèi)容先刷新到磁盤中去。之后,緩存的內(nèi)容就無效了。不過,重建的時候并不阻止那些需要使用指向到緩存中的索引的查詢。相反地,服務(wù)器使用本地文件系統(tǒng)緩存直接訪問數(shù)據(jù)表索引。文件系統(tǒng)緩存不如索引緩存來的高效,因此,可以預(yù)見這時的查詢會比較慢。一旦緩存重建完了,指向它的索引又可以使用了,同時也就不再使用文件系統(tǒng)緩存來訪問索引了。
7.4.7 MySQL 如何統(tǒng)計打開的表數(shù)量
執(zhí)行命令 mysqladmin status
時,可以看到類似如下結(jié)果:
Uptime: 426 Running threads: 1 Questions: 11082 Reloads: 1 Open tables: 12
如果只有6個數(shù)據(jù)表時,Open tables
的值確實12,這可能會有些疑惑。
MySQL是多線程的,因此可能會有多個客戶端同時發(fā)起查詢某個表的請求。為了最小化多個客戶端線程在同一個表上的不同狀態(tài),針對每個并發(fā)的線程單獨打開數(shù)據(jù)表。這會占用一些內(nèi)存,但是通常會提高性能。如果是 MyISAM
表,針對每個打開數(shù)據(jù)表的客戶端都要有一個額外的文件描述符打開數(shù)據(jù)文件的情況(不過索引文件的描述符則可以在所有的線程間共享)。ISAM
存儲引擎則共享這些操作。
想要了解更多,可以看下一章的詳細(xì)內(nèi)容"7.4.8 How MySQL Opens and Closes Tables"。
7.4.8 MySQL 如何打開和關(guān)閉數(shù)據(jù)表
系統(tǒng)變量 table_cache
, max_connections
和 max_tmp_tables
影響著服務(wù)器保持打開的文件最大數(shù)量。提高一個或多個這些變量,就可以提高操作系統(tǒng)在每次處理時能打開的文件描述符限制。很多操作系統(tǒng)都可以增加文件打開的數(shù)量限制,不過每個系統(tǒng)的方法都各不一樣。查閱一下操作系統(tǒng)文檔來判斷是否可以提高限制以及怎么去做。
table_cache
和 max_connections
相關(guān)。例如,有200個并發(fā)的連接,那么則必須至少有 200 * N
大小的表緩存,N 是在一個表連接中最大的表數(shù)量。同時也需要為臨時表和臨時文件保留一些額外的文件描述符。
請確認(rèn)操作系統(tǒng)可以通過設(shè)定 table_cache
來處理對應(yīng)數(shù)量的打開文件。如果 table_cache
設(shè)得太高了,MySQL可能會用完全部的文件描述符而拒絕新連接,就無法執(zhí)行查詢,變得很不可靠。因此必須要考慮到 MyISAM
存儲引擎要為每個獨立打開的表使用兩個文件描述符??梢栽趩?mysqld_safe
的時候增加 --open-files-limit
參數(shù)來提高M(jìn)ySQL打開文件描述符的數(shù)量。詳情請看"A.2.17 File Not Found".
緩存中會保存 table_cache
個打開的表會。它的默認(rèn)值是64;它可以在啟動 mysqld
的時候通過修改 --table_cache
參數(shù)來改變。注意,MySQL可能會臨時打開比這個數(shù)更多的表來執(zhí)行查詢。
在以下情況中,一個不用的表會被關(guān)閉并且從緩存中被刪除:
- 如果緩存滿了,并且一個線程試圖打開一個不在緩存中的表。
- 如果緩存中已經(jīng)包含了不止
table_cache
個表目,并且線程無需再使用該表。 - 當(dāng)發(fā)生刷新表操作。當(dāng)提交一個
FLUSH TABLES
語句或者執(zhí)行mysqladmin flush-tables
ormysqladmin refresh
命令時就會這樣。
當(dāng)表緩存滿了,服務(wù)器遵循以下步驟來分配一個新的緩存表目:
- 當(dāng)前沒使用的表都釋放,依照最近最少使用的順序。
- 如果有一個新的表要被打開,但是緩存滿了且沒有表被釋放,緩存就臨時根據(jù)需要擴(kuò)充一下。
當(dāng)緩存處于臨時擴(kuò)充狀態(tài),且有表處于從使用變成不使用狀態(tài)時,就關(guān)閉這個表并且從緩存中釋放。
每個并行訪問都打開一個表。這意味著當(dāng)有兩個線程同時訪問一個表,或者一個線程在同一個查詢中訪問兩次這個表(例如,在表連接中連接自己),那么這個表就需要被打開兩次。每次并發(fā)的打開都在表緩存中請求一個表目。每個表第一次打開時都需要兩個文件描述符:一個給數(shù)據(jù)文件,一個給索引文件。其他新增的對該表的打開則只需要一個文件,給數(shù)據(jù)文件。索引文件的描述符在所有的線程間是共享的。
如果使用 HANDLER tbl_name OPEN
語句打開表,則有一個專用的表對象給該線程。這個表對象不和其他線程共享,并且除非調(diào)用 HANDLER tbl_name CLOSE
語句或者線程結(jié)束,否則它不會關(guān)閉;這樣的話,它就重新放回表索引中(如果索引還未滿)。詳情請看"14.1.3 HANDLER
Syntax"。
可以檢查mysqld
的狀態(tài)變量 Opened_tables
來判斷表索引是否太小了:
mysql> SHOW STATUS LIKE 'Opened_tables'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | Opened_tables | 2741 | +---------------+-------+
如果這個值比較大,不過完全沒必要執(zhí)行一大堆的 FLUSH
語句。只需加大表索引緩存大小即可。詳情請看"5.2.3 Server System Variables"和"5.2.4 Server Status Variables"。
TABLES
7.4.9 在一些數(shù)據(jù)庫中創(chuàng)建太多表的缺點
如果在一個目錄下有很多的 MyISAM
或 ISAM
表,打開,關(guān)閉,創(chuàng)建等操作就會變慢。如果在很多不同的表上執(zhí)行SELECT
語句,當(dāng)表緩存滿了之后這就會有些開銷,因為每個表都需要被打開,其他的必須關(guān)閉。可以加大表緩存來降低這個開銷。