課程「性能優化之MySQL優化」的複習筆記

能夠進行優化的層面mysql

  • 硬件
  • 系統配置
  • 數據庫表結構
  • SQL 語句和索引

進行優化前的數據準備linux

打開如下連接下載數據sql

http://downloads.mysql.com/docs/sakila-db.zip

打開終端,執行如下命令數據庫

# 登陸 MySQL Cli 模式
mysql -uroot -p

# 建立數據庫
SOURCE /Users/LuisEdware/Downloads/sakila-db/sakila-schema.sql.sql

# 填充數據到數據庫
SOURCE /Users/LuisEdware/Downloads/sakila-db/sakila-data.sql

# 使用 sakila 數據庫
USE sakila;

SQL 語句和索引服務器

MySQL 慢查詢日誌

如何發現有問題的 SQL?答案是使用 MySQL 慢查詢日誌對有效率問題的 SQL 進行監控,執行命令以下:網絡

# 查看是否開啓慢查詢日誌
show variables like "slow_query_log";

# 查看是否設置了把沒有索引的記錄到慢查詢日誌
show variables like "log_queries_not_using_indexes";

# 查看是否設置慢查詢的 SQL 執行時間
show variables like "long_query_time";

# 查看慢查詢日誌記錄位置
show variables like "slow_query_log_file";

# 開啓慢查詢日誌
set global slow_query_log=on

# 設置沒有索引的記錄到慢查詢日誌
set global log_queries_not_using_indexes=on

# 設置到慢查詢日誌的 SQL 執行時間
set global long_query_time=0

# 查看慢查詢日誌(在 Linux 終端下執行)
tail -50 /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log;

慢查詢日誌所包含的內容tcp

  • SQL 的執行時間:# Time: 2016-10-13T10:01:45.914267Z
  • SQL 的執行主機:# User@Host: root[root] @ localhost [] Id: 949
  • SQL 的執行信息:# Query_time: 0.000227 Lock_time: 0.000099 Rows_sent: 2 Rows_examined: 2
  • SQL 的執行時間:SET timestamp=1476352905;
  • SQL 的執行內容:*select from store;**

慢查詢日誌分析工具函數

  • mysqldumpslow
    • 安裝:MySQL 數據庫自帶
    • 使用:mysqldumpslow /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log;
    • 選項
    • 參數
  • pt-query-digest
    • 安裝:brew install brew install percona-toolkit
    • 使用:pt-query-digest /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log | more;
    • 選項
    • 參數

如何經過慢查詢日誌發現有問題的 SQL?工具

  • 查詢次數多且每次查詢佔用時間長的 SQL
  • IO 大的 SQL
  • 未命中索引的 SQL

EXPLAIN 分析 SQL 的執行計劃

使用 EXPLAIN 分析 SQL 的執行計劃的例子以下:性能

EXPLAIN SELECT * FROM staff;

使用 EXPLAIN 分析 SQL 的各列參數含義以下:

  • id:SQL 語句執行順序編號
  • select_type:SQL 語句執行的類型,主要區別普通查詢、聯合查詢和子查詢之類的複雜查詢
  • table:SQL 語句執行所引用的數據表
  • type:顯示鏈接使用的類型
  • possible_keys:指出 MySQL 能在該數據表中使用哪些索引有助於查詢
  • key:SQL 語句執行時所使用的索引
  • key_len:SQL 語句執行時所使用的索引的長度。在不損失精確性的狀況下,長度越短越好
  • ref:顯示索引的哪一列被使用了
  • rows:MySQL 認爲必須檢查的用來返回請求數據的行數
  • Extra:提供 MySQL 優化器一系列額外信息

MAX() 和 COUNT() 的優化

MAX()

分析 SQL 語句:使用 MAX() 方法查詢最後一筆交易的時間

EXPLAIN SELECT MAX(payment_date) FROM payment_date

執行結果以下:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: payment
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 16086
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

若是數據表的數據很是大,查詢頻率又很是高,那麼服務器的 IO 消耗也會很是高,因此這條 SQL 語句須要優化。能夠經過創建索引進行優化。執行代碼以下:

CREATE INDEX idx_paydate ON payment(payment_date);

而後再分析 SQL 語句,執行結果以下:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: NULL
   partitions: NULL
         type: NULL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
     filtered: NULL
        Extra: Select tables optimized away
1 row in set, 1 warning (0.01 sec)

通過優化以後,因爲索引是按順序排列的,MySQL 不須要查詢表中的數據,而是經過查詢索引最後的一個數據,就能夠得知執行結果了。並且這個時候,無論表的數據量多大,查詢 MAX() 所須要的時間都是基本固定的,這樣就儘量地減小了 IO 操做。

COUNT()

分析 SQL 語句:使用 COUNT() 函數在一條 SQL 中同時查出 2006 年和 2007 年電影的數量

SELECT
    count(release_year = '2006' OR NULL) AS '2006 年電影數量' ,
    count(release_year = '2007' OR NULL) AS '2007 年電影數量'
FROM
    film;

count(*) 包含空值,count(id) 不包含空值。上述語句就是優化 Count() 函數取值

子查詢

分析 SQL 語句:查詢 sandra 出演的全部影片

SELECT
    title ,
    release_year ,
    LENGTH
FROM
    film
WHERE
    film_id IN(
        SELECT
            film_id
        FROM
            film_actor
        WHERE
            actor_id IN(
                SELECT
                    actor_id
                FROM
                    actor
                WHERE
                    first_name = 'sandra'
            )
    )

一般狀況下,須要把子查詢優化爲 join 查詢,但在優化時要注意關聯鍵是否有一對多的關係,要注意重複數據。

GROUP BY

group by 可能會出現臨時表、文件排序等,影響效率。能夠經過關聯的子查詢,來避免產生臨時表和文件排序,能夠節省 IO。

group by 查詢優化前:

EXPLAIN SELECT
    actor.first_name ,
    actor.last_name ,
    Count(*)
FROM
    sakila.film_actor
INNER JOIN sakila.actor USING(actor_id)
GROUP BY
    film_actor.actor_id;

執行結果以下:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
   partitions: NULL
         type: ALL
possible_keys: PRIMARY
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 200
     filtered: 100.00
        Extra: Using temporary; Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor
   partitions: NULL
         type: ref
possible_keys: PRIMARY,idx_fk_film_id
          key: PRIMARY
      key_len: 2
          ref: sakila.actor.actor_id
         rows: 27
     filtered: 100.00
        Extra: Using index
2 rows in set, 1 warning (0.01 sec)

group by 查詢優化後:

EXPLAIN SELECT
    actor.first_name ,
    actor.last_name ,
    c.cnt
FROM
    sakila.actor
INNER JOIN(
    SELECT
        actor_id ,
        count(*) AS cnt
    FROM
        sakila.film_actor
    GROUP BY
        actor_id
) AS c USING(actor_id);

執行結果以下:

*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: actor
   partitions: NULL
         type: ALL
possible_keys: PRIMARY
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 200
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: PRIMARY
        table: <derived2>
   partitions: NULL
         type: ref
possible_keys: <auto_key0>
          key: <auto_key0>
      key_len: 2
          ref: sakila.actor.actor_id
         rows: 27
     filtered: 100.00
        Extra: NULL
*************************** 3. row ***************************
           id: 2
  select_type: DERIVED
        table: film_actor
   partitions: NULL
         type: index
possible_keys: PRIMARY,idx_fk_film_id
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 5462
     filtered: 100.00
        Extra: Using index
3 rows in set, 1 warning (0.00 sec)

LIMIT

LIMIT 經常使用於分頁處理,時常會伴隨 ORDER BY 從句使用,所以大多時候會使用 Filesorts ,這樣會形成大量的 IO 問題

優化步驟1:使用有索引的列或主鍵進行 Order By 操做
優化步驟2:記錄上次返回的主鍵,在下次查詢時使用主鍵過濾(保證主鍵是自增且有索引)

索引優化

1.爲合適的列創建索引

  • 在 where 從句,group by 從句,order by 從句,on 從句中出現的列
  • 索引字段越小越好
  • 離散度的列放到聯合索引的前面

例如:

SELECT * FROM payment WHERE staff_id = 2 AND customer_id = 584;

上述 SQL 語句,是 index(staff_id,customer_id) 合理,仍是 index(customer_id,staff_id) 合理。執行語句以下:

SELECT count(DISTINCT customer_id) , count(DISTINCT staff_id) FROM payment;

-- 結果是 599  2

因爲 customer_id 的離散度更大,因此應該使用 index(customer_id,staff_id)

2.找到重複和冗餘的索引

之因此要找到重複和冗餘的索引,是由於過多的索引不但影響寫入,並且影響查詢,索引越多,分析越慢。那麼爲什麼重複索引、冗餘索引?概念以下:

重複索引是指相同的列以相同的順序創建的同類型的索引,以下表中 primary key 和 ID 列上的索引就是重複索引,例子以下:

CREATE TABLE test(
    id INT NOT NULL PRIMARY KEY ,
    NAME VARCHAR(10) NOT NULL ,
    title VARCHAR(50) NOT NULL ,
    UNIQUE(id)
) ENGINE = INNODB;

UNIQUE(ID) 和 PRIMARY KEY 重複了。

冗餘索引是指多個索引的前綴列相同,或是在聯合索引中包含了主鍵的索引,例子以下:

CREATE TABLE test(
    id INT NOT NULL PRIMARY KEY ,
    NAME VARCHAR(10) NOT NULL ,
    title VARCHAR(50) NOT NULL ,
    KEY(NAME , id)
) ENGINE = INNODB;

查找重複及冗餘索引的 SQL 語句以下:

USE information_schema;

SELECT
    a.TABLE_SCHEMA AS '數據名' ,
    a.table_name AS '表名' ,
    a.index_name AS '索引1' ,
    b.INDEX_NAME AS '索引2' ,
    a.COLUMN_NAME AS '重複列名'
FROM
    STATISTICS a
JOIN STATISTICS b ON a.TABLE_SCHEMA = b.TABLE_SCHEMA
AND a.TABLE_NAME = b.table_name
AND a.SEQ_IN_INDEX = b.SEQ_IN_INDEX
AND a.COLUMN_NAME = b.COLUMN_NAME
WHERE
    a.SEQ_IN_INDEX = 1
AND a.INDEX_NAME <> b.INDEX_NAME

也可使用工具 pt-duplicate-key-checker 檢查重複索引和冗餘索引,使用例如:

pt-duplicate-key-checker -uroot -p '123456' -h 127.0.0.1 -d sakila

執行結果以下:

# ########################################################################
# Summary of indexes
# ########################################################################

# Size Duplicate Indexes   118425374
# Total Duplicate Indexes  24
# Total Indexes            1439

3.刪除不用的索引

目前 MySQL 中尚未記錄索引的使用狀況,可是在 PerconMySQL 和 MariaDB 中能夠經過 INDEX_STATISTICS 表來查看哪些索引未使用,但在 MySQL 中目前只能經過慢查詢日誌配合共組 pt-index-usage 來進行索引使用狀況的分析。

pt-index-usage -uroot -p '123456' /usr/local/var/mysql/luyiyuandeMacBook-Pro-slow.log;

數據庫結構優化

選擇合適的數據類型

  • 使用能夠存下你的數據的最小的數據類型
  • 使用簡單的數據類型。Integer 要比 varchar 類型在 MySQL 中處理更高效
  • 儘量使用 not null 定義字段
  • 儘可能少用 text 類型,非用不可時最好考慮分表

數據庫表的範式化優化與反範式化優化

數據庫表的垂直拆分

垂直拆分,就是把原來一個有不少列的表拆分紅多個表,這解決了表的寬度問題。一般垂直拆分就能夠按如下原則進行:

  • 把不太經常使用的字段單獨存放到一個表中
  • 把大字段獨立存放到一個表中
  • 把常常一塊兒使用的字段放到一塊兒

數據庫表的水平拆分

當單表的數據量過大,致使增刪查改等操做過慢,這時候須要對錶進行水平拆分。水平拆分的表,每一張表的結構都是徹底一致的。

經常使用的水平拆分方法爲:

  1. 對 customer_id 進行 hash 運算,若是要拆分紅 5 個表則使用 mod(customer_id,5) 取出 0-4 個值
  2. 針對不一樣的 hashID 把數據存到不一樣的表中

挑戰:

  • 跨分區表進行數據查詢
  • 統計及後臺報表操做

系統配置優化

數據庫系統配置優化

數據庫是基於操做系統的,目前大多數 MySQL 都是安裝在Linux 系統之上,因此對於操做系統的一些參數配置也會影響到 MySQL 的性能,下面列舉一些經常使用到的系統配置。

網絡方面的配置,要修改文件 /etc/sysctl.conf

# 增長 tcp 支持的隊列數
net.ipv4.tcp_max_syn_backlog = 65535

# 減小斷開鏈接時,資源回收
net.ipv4.tcp_max_tw_buckets = 8000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 10

打開文件數的限制,可使用 ulimit -a 查看目錄的各項限制,能夠修改文件 /etc/security/limits.conf ,增長如下內容以修改打開文件數量的限制

soft nofile 65535
hard nofile 65535

除此以外最好在 MySQL 服務器上關閉 iptables,selinux 等防火牆軟件。

MySQL 配置文件

MySQL 能夠經過啓動時指定配置參數和使用配置文件兩種方法進行配置,在通常狀況下,配置文件位於 /etc/my.cnf 或是 /etc/mysql/my.cnf,MySQL 查詢配置文件的順序是能夠經過如下方法過的

經常使用參數說明

  • innodb_buffer_pool_size:用於配置 Innodb 的緩衝池
    • 若是數據庫中只有 Innodb 表,則推薦配置量爲總內存的 75%
    • Innodb_buffer_pool_size >= Total MB
      SELECT
      ENGINE ,
      round(
      sum(data_length + index_length) / 1024 / 1024 ,
      1
      ) AS 'Total MB'
      FROM
      information_schema. TABLES
      WHERE
      table_schema NOT IN(
      "information_schema" ,
      "performance_schema"
      )
      GROUP BY
      ENGINE;
  • innodb_buffer_pool_instances:MySQL 5.5 中新增參數,能夠控制緩衝池的個數,默認狀況下只有一個緩衝池。
  • innodb_log_buffer_size:Innodb 日誌緩衝的大小,因爲日誌最長,每秒鐘就會刷新,因此通常不用太大。
  • innodb_flush_log_at_trx_commit:對 Innodb 的 IO 效率影響很大。
  • innodb_file_per_table:控制 Innodb 每個表都使用獨立的表空間,默認爲 OFF,也就是全部表都會創建在共享表空間中。
  • innodb_stats_on_metadata:決定 MySQL 在什麼狀況下會刷新 innodb 表的統計信息。

第三方配置工具使用

percona:https://tools.percona.com/

服務器硬件優化

  • 如何選擇 CPU
    • MySQL 有一些工做只能使用到單核 CPU,選擇高頻
    • MySQL 對 CPU 核數的支持並非越多越快,MySQL 5.5 版本不要超過 32 個核
  • 硬盤 IO 優化
    • RAID 級別簡介
    • RAID 0:也稱爲條帶,就是把多個磁盤連接成一個硬盤使用,這個級別 IO 最好
    • RAID 1:也成爲鏡像,要求至少兩個磁盤,每組磁盤存儲的數據相同
    • RAID 1 + 0:就是 RAID 1 和 RAID 0的結合。同時具有兩個級別的優缺點。通常建議數據庫使用這個級別。
    • RAID 5:把多個(最少 3 個)硬盤合併成 1 個邏輯盤使用,數據讀寫時會創建奇偶校驗信息,而且奇偶校驗信息和相對應的數據分別存儲在不一樣的磁盤上。當 RAID 5 的一個磁盤數據發生損壞後,利用剩下的數據和相應的奇偶校驗信息去恢復被損壞的數據。
相關文章
相關標籤/搜索