MySQL 內存優化

1、內存優化原則

  • 將盡可能多的內存分配給 MySQL 作緩存,但要給操做系統和其餘程序的運行預留足夠的內存。
  • myisam 的數據文件讀取依賴於操做系統自身的 I/O 緩存,所以,若是有 myisam 表,要預留更多的內存給操做系統作 IO 緩存。
  • 排序區,鏈接區等緩存是分配給每一個數據庫會話(session)專用的,其默認值的設置要根據最大鏈接數合理分配,若是設置過大,不但浪費內存資源,並且在併發鏈接較高時會致使物理內存耗盡。

2、myisam 內存優化

myisam 存儲引擎使用 key buffer 緩存索引塊,以加速 myisam 索引的讀寫速度,對於 myisam 表的數據塊,MySQL 沒有特別的患處機制,徹底依賴操做系統的 IO 緩存。html

1. key_buffer_size 設置

key_buffer_size 決定 myisam 索引塊緩存區的大小,它直接影響 myisam 表的存取效率。能夠在 MySQL 的參數文件中設置該值。
對弈通常數據庫服務器,建議至少將 1/4 可用內存分配給 key_buffer_size。 mysql

能夠經過檢查 key_read_requests、key_write_requests 和 key_writes 等 MySQL 狀態變量來評估索引緩存的效率。
通常來講,索引塊物理讀比率:
key_reads / key_read_requests 應小於0.01
索引塊寫比率也應儘量小,但這與應用特色有關,對於更新和刪除操做特別多的應用,key_writes / key_write_requests 可能會接近 1,而對於每次更新不少行記錄的應用,key_writes / key_write_requests 就會比較小。 算法

除經過索引塊的物理讀寫比率衡量 key buffer 外,也能夠經過評估 key buffer 的使用率來判斷索引緩存設置是否合理。
key buffer 使用率的計算公式以下:
1 - ((key_blocks_unused * key_cache_block_size) / key_buffer_size)
通常來講,使用率在 80% 左右比較合適,大於 80% 可能因索引緩存不足而致使性能降低;小於 80% 會致使內存浪費。sql

2. 使用多個索引緩存

MySQL 經過各 session 共享的 key buffer 提升了 myisam 索引存取的性能,但它並不能消除 session 間對 key buffer 的競爭。好比,一個 session 若是對某個很大的索引進行掃描,就可能將其餘的索引數據塊擠出索引緩存區,而這些索引塊多是其餘 session 要用的熱數據。
爲減小 session 間對 key buffer 的競爭,MySQL 從 5.1 版本開始引入了多緩存的機制,從而能夠將不一樣表的索引緩存到不一樣的key buffer 中:數據庫

# hot_cache 是新建索引緩存的命名,global 關鍵字表示新建緩存對每個新的鏈接都有效。
mysql> set global hot_cache.key_buffer_size = 128*1024;

# 刪除剛剛建立的索引緩存
mysql> set global hot_cache.key_buffer_size = 0;

默認狀況下,MySQL 將使用默認的key buffer 緩存 myisam 表的索引,能夠用 cache index 命令指定表的索引緩存:緩存

MySQL [sakila]> create table t2 (id int , name varchar(30)) engine myisam;
Query OK, 0 rows affected (0.03 sec)

MySQL [sakila]> create table t1 (id int , name varchar(30)) engine myisam;
Query OK, 0 rows affected (0.00 sec)

MySQL [sakila]> cache index t1,t2 in hot_cache;
+-----------+--------------------+----------+----------+
| Table     | Op                 | Msg_type | Msg_text |
+-----------+--------------------+----------+----------+
| sakila.t1 | assign_to_keycache | status   | OK       |
| sakila.t2 | assign_to_keycache | status   | OK       |
+-----------+--------------------+----------+----------+
2 rows in set (0.00 sec)

更常見的作法是經過配置文件在 MySQL 啓動時自動建立並加在索引緩存:服務器

key_buffer_size = 4G
hot_cache.key_buffer_size = 2G
cold_cache.key_buffer_size = 1G
init_file=/path/mysqld_init.sql

在 mysql_init.sql 文件中,能夠經過 cache index 命令分配索引緩存,並用 load index into cache 命令來進行索引預加載:session

cache index sales in hot_cache;
cache index sales2 in cold_cache;
load index into cache sales,sales2

3. 調整「中點插入策略」

中點插入策略
中點插入策略(midpoint insertion strategy)是對簡單 lru 淘汰算法的改進,它將 lru 鏈分紅兩部分:hot 子表和 warm 子表,當一個索引塊讀入內存時,先放到 lru 鏈表的「中點」,即 warm 子表的尾部,當達到必定的命中次數後,該索引塊會被晉升到 hot 子表的尾部;此後,該數據塊在 hot 子表流轉,若是其到達 hot 子表的頭部並超過必定時間,它將由 hot 子表的頭部降級到 warm 子表的頭部;當須要淘汰索引塊時,緩存管理程序會優先淘汰 warm 表頭部的內存塊。這種算法可以避免偶爾被訪問的索引塊將訪問頻繁的熱塊淘汰。 併發

能夠經過調節 key_cache_division_limit 來控制多大比例的緩存用作 warm 子表,其默認值是 100,意思是所有緩存塊都放在 warm 子表,其實也就是不啓用「中點插入策略」。若是咱們但願將大體 30% 的緩存用來 cache 最熱的索引塊,能夠對作以下設置高併發

set global key_cache_division_limit = 70  
set global hot_cache key_cache_division_limit = 70

除了調節 warm 子表的比例外,還能夠經過 key_cache_age_threshold 控制數據塊由 hot 子表向 warm 子表降級的時間,值越小,數據塊就越快被降級。對於有 N 個塊的索引緩存來講,若是一個在 hot 子表頭部的索引塊,在最後 N * key_cache_age_threshold / 100 次緩存命中內未被訪問過,就會被降級到 warm 子表。

4. 調整 read_buffer_size 和 read_rnd_buffer_size

若是須要常常順序掃描 myisam 表,能夠經過增大 read_buffer_size 的值來改善性能,但須要注意的是:read_buffer_size 是每一個 session 獨佔的,若是默認值太大,就會形成內存浪費,甚至致使物理內存耗盡。
對於須要作排序的 myisam 表查詢,如帶有 order by 子句的 sql, 適當增大 read_rnd_buffer_size 的值,也能夠改善此類 sql 的性能。read_rnd_buffer_size 也是按 session 分配的,默認值不能太大。

3、 innodb 內存優化

1. innodb 緩存機制

innodb 用一塊內存區作 io 緩存池,該緩存池不只用來緩存 innodb 的索引塊,也用來緩存 innodb 的數據塊,這一點與 myisam 不一樣。

在內部,innodb 緩存池邏輯上由 free list、flush list 和 lru list 組成:

  • free list : 空閒緩存塊列表
  • flush list : 是須要刷新到此磁盤的緩存塊列表
  • lru list : 是 innodb 正在使用的緩存塊,它是 innodb buffer pool 的核心。

innodb 使用的 lru 算法與 myisam 的「中點插入策略」lru算法很相似,大體原理是:將 lru list 分爲 young sublist 和 old sublist,數據從磁盤讀入時,會將該緩存塊插入到 lru list 的「中點」,即 old sublist 的頭部;通過必定時間的訪問(由 innodb_old_blocks_time 系統參數決定),該數據塊將會由 old sublist 轉移到 young sublist 的頭部,也就是整個lru list 的頭部;隨着時間推移,young sublist 和 old sublist 中較少被訪問的緩存塊將從各自鏈表的頭部逐漸向尾部移動;須要淘汰數據塊時,優先從鏈表尾部淘汰。這種設計一樣是爲了防止偶爾被訪問的索引塊將訪問頻繁的熱塊淘汰。

2. innodb_buffer_pool_size 的設置

innodb_buffer_size 決定 innodb 存儲引擎表數據和索引數據的最大緩存區大小。在保證操做系統及其餘程序有足夠內存可用的狀況下,innodb_buffer_pool_size 的值越大,緩存命中率越高,訪問 innodb 表須要的磁盤 io 就越少,性能也就越高。在一個專用的數據庫服務器上,能夠將 80% 的物理內存分配給 innodb buffer pool。

經過如下命令查看 buffer pool 的使用狀況:

zj@bogon:/usr/local/mysql$ mysqladmin -u root -p -S /tmp/mysql.sock ext | grep -i innodb_buffer_pool
Enter password: 
| Innodb_buffer_pool_dump_status                | Dumping of buffer pool not started               |
| Innodb_buffer_pool_load_status                | Buffer pool(s) load completed at 170918 15:07:09 |
| Innodb_buffer_pool_resize_status              |                                                  |
| Innodb_buffer_pool_pages_data                 | 456                                              |
| Innodb_buffer_pool_bytes_data                 | 7471104                                          |
| Innodb_buffer_pool_pages_dirty                | 0                                                |
| Innodb_buffer_pool_bytes_dirty                | 0                                                |
| Innodb_buffer_pool_pages_flushed              | 39                                               |
| Innodb_buffer_pool_pages_free                 | 32312                                            |
| Innodb_buffer_pool_pages_misc                 | 0                                                |
| Innodb_buffer_pool_pages_total                | 32768                                            |
| Innodb_buffer_pool_read_ahead_rnd             | 0                                                |
| Innodb_buffer_pool_read_ahead                 | 0                                                |
| Innodb_buffer_pool_read_ahead_evicted         | 0                                                |
| Innodb_buffer_pool_read_requests              | 3329                                             |
| Innodb_buffer_pool_reads                      | 422                                              |
| Innodb_buffer_pool_wait_free                  | 0                                                |
| Innodb_buffer_pool_write_requests             | 515                                              |
zj@bogon:/usr/local/mysql$

可用如下公式計算 innodb 緩存池的命中率:

(1 - innodb_buffer_pool_read / innodb_buffer_pool_read_request) * 100

若是命中率過低,則應考慮擴充內存,增長 innodb_buffer_pool_size 的值。

3. 調整 old sublist 大小

調整 old_sublist 的比例由系統參數 innodb_old_blocks_pct 決定,其取值範圍是 5 ~ 95, 默認值是 37。
經過如下命令能夠查看其當前設置:

MySQL [(none)]> show global variables like '%innodb_old_blocks_pct%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |
+-----------------------+-------+
1 row in set (0.00 sec)

4. 調整 innodb_old_blocks_time 的設置

innodb_old_blocks_time 參數決定了緩存數據塊由 old sublist 轉移到 young sublist 的快慢,當一個緩存數據塊被插入到 midpoint(old sublist)後,至少要在 old sublist 停留超過 innodb_old_blocks_time(ms)後,纔有可能被轉移到 young list。

5. 調整緩存池數量,減小內部對緩存池數據源結構的爭用

MySQL 內部不一樣線程對 innodb 緩存池的訪問在某些階段是互斥的,這種內部競爭也會產生性能問題, 尤爲在高併發和 buffer pool 較大的狀況下。爲解決這個問題,innodb 的緩存系統引入了 innodb_buffer_poolinstances 配置參數,對於較大的緩存池,適當增大此參數的值,能夠下降併發致使的內部緩存訪問衝突,改善性能。innodb 緩存系統會將參數 innodb_buffer_pool_size 指定大小的緩存平分爲 innodb_buffer_pool_instances 個 buffer pool。

6. 控制 innodb buffer 刷新,延長數據緩存事件,減緩磁盤 I/O

在 innodb 找不到乾淨可用緩存頁或檢查點被觸發等狀況下,innodb 的後臺線程就會開始把「髒的緩存頁」回寫到磁盤文件中,這個過程叫緩存刷新
·
一般都但願 buffer pool 中的數據在緩存中停留的時間儘量長,以備重用,從而減小磁盤 IO 的次數。磁盤 IO 慢,是數據庫系統最主要的性能瓶頸,能夠經過延遲緩存刷新來減輕 IO 壓力。

innodb buffer pool 的刷新快慢主要取決於兩個參數。

innodb_max_dirty_pages_pct

它控制緩存池中髒頁的最大比例,默認是 75% ,若是髒頁的數量達到或超過該值,innodb 的後臺線程將開始緩存刷新。

innodb_io_capacity

它表明磁盤系統的 IO 能力,其值在必定程度上表明磁盤每秒可完成 IO 的次數。innodb_io_capacity 默認值是 200,對於低轉速的磁盤,如 7200RPM 的磁盤,可將該值下降到 100,而對於固態硬盤和由多個磁盤組成的盤陣,它的值能夠適當增大。

innodb_io_capacity 決定一批刷新髒頁的數量,當緩存池髒頁的比例達到 innodb_max_dirty_pages_pct 時,innodb 大約將 innodb_io_capacity 個已改變的緩存頁刷新到磁盤。在合併插入緩存時,innodb 每次合併的頁數是 0.05 * innodb_io_capacity。
若 innodb_buffer_pool_wait_free 的值增加較快,則說明 innodb 常常在等待空閒緩存頁,若是沒法增大緩存池,那麼應將 innodb_max_dirty_pages_pct 的值調小,或將innodb_io_capacity 的值提升,以加快髒頁的刷新。

7. innodb doublewrite

當進行髒頁刷新時,innodb 採用了雙寫(double write)策略,這麼作的緣由是:MySQL 的數據頁大小(通常是 16KB)與操做系統的 IO 數據頁大小(通常是 4KB)不一致,沒法保證 innodb 緩存頁被完整、一致的刷新到磁盤,而innodb 的 redo 日誌只記錄了數據頁改變的部分,並未記錄數據頁的完整前像,當發生部分寫或斷裂寫時(好比講緩存頁的第一個 4KB 寫入磁盤後,服務器斷電),就會出現頁面沒法恢復的問題,爲解決這個問題,innodb 引入了 doublewrite 技術。

doublewrite 機制的實現原理:

用系統表空間的一塊連續磁盤空間(100個連續數據頁,大小爲 2MB)做爲 doublewrite buffer,當進行髒頁刷新時,首先將髒頁的副本寫到系統表空間的 doublewrite buffer 中,而後調用 fsync() 刷新操做系統 IO 緩存,確保副本被真正寫入磁盤,最後 innodb 後臺 IO 線程將髒頁刷新到磁盤數據文件。

在作恢復時,若㘝發現不一致的頁,innodb 會用系統表空間 doublewrite buffer 區的相應副原本恢復數據頁。

4、 調整用戶服務線程排序緩衝區

若是經過 show global status 看到 sort_merge_passes 的值很大,能夠考慮經過調整參數 sort_buffer_size 的值來增大排序緩存區,以改善帶有 order by 子句或 group 子句 sql 的性能。

對於沒法經過索引進行鏈接操做的查詢,能夠嘗試經過增大 join_buffer_size 的值來改善性能。

不過須要注意的是,sort buffer 和 join buffer 都是面向客戶線程分配的,若是設置過大可能形成內存浪費。

最好的策略是:設置較小的全局 join_buffer_size,而對須要作複雜鏈接操做的 session 單獨設置較大的 join_buffer_size。

相關文章
相關標籤/搜索