它的建議是機器核數乘以 2 加 1。也就是說,4 核的機器,連接池維護 9 個連接就夠了。
這個公式從一定程度上來說對其他數據庫也是適用的。
爲什麼有的情況下,減少連接數反而會提升吞吐量呢?
爲什麼建議設置的連接池大小要跟 CPU 的核數相關呢?
每一個連接,服務端都需要創建一個線程去處理它。連接數越多,服務端創建的線程數就越多。
CPU的核數是有限的,執行多個線程,頻繁切換(線程)上下文會造成比較大的性能開銷。
不管是數據庫本身的配置,還是按照這個數據庫服務的操作系統的配置,
對配置進行優化,最終的目的都是使硬件本身的性能更好發揮,包括CPU、內存、磁盤、網絡。
在之前的內容中也接觸了很多的MySQL和InnoDB的配置參數,包括各種開關和數值的配置,
大多數參數都提供了一個默認值,比如默認的buffer_pool_size,默認的頁大小,InnoDB的併發線程數等等。
這些默認配置可以瞞住大部分情況的需求,除非有特殊的需求,在清楚參數的含義時再去修改它。
修改配置的工作一般由專業的DBA完成。
這是官網系統的參數列表,需要時再做參考:
https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html
除了合理設置服務端連接數和客戶端連接池大小外,還有哪些減少客戶端和數據庫服務端的連接數的方案呢?
可以引入緩存。
三、緩存——架構優化
3.1 緩存
在應用的併發數非常大的情況下,如果沒有緩存,會造成兩個問題:
一是會給數據庫帶來很大壓力,二是對於應用來說操作數據的速度也會受影響。
可以採用第三方緩存來解決這個問題,比如 Redis。
運行獨立的緩存服務,屬於架構層面的優化
爲了減少單臺數據庫服務器的讀寫壓力,在架構層面還可以做哪些其他優化措施?
還可以採取主從複製、分庫分表等方案。
四、優化器——SQL語句分析優化
優化器就是對我們的 SQL 語句進行分析,生成執行計劃。
4.1 慢查詢日誌(slow query log)
我們可以通過慢查詢日誌知道服務中哪些SQL語句比較慢。
因爲開啓慢查詢日誌是有代價的(和bin log、optimizer-trace一樣),所以默認關閉。
show variables like '%slow_query%';
除了這個開關,還有一個參數,控制執行多久的SQL才被記錄到慢日誌,默認是10秒:
show variables like '%long_query%';
可以直接動態修改參數(重啓後失效):
set @@global.slow_query_log=1; -- 慢查詢日誌開關 1 開啓,0 關閉,重啓後失效
set @@global.long_query_time=3; -- mysql 默認的慢查詢時間是 10 秒,另開一個窗口後纔會查到最新值
或者修改配置文件 my.cnf,讓配置永久生效。
以下配置定義了慢查詢日誌的開關、慢查詢的時間、慢日誌文件的存放路徑。
slow_query_log = ON
long_query_time=2
slow_query_log_file =/var/lib/mysql/localhost-slow.log
show global status like 'slow_queries'; -- 查看有多少慢查詢
show variables like '%slow_query%'; -- 獲取慢日誌目錄
雖然有了慢日誌,但是慢日誌記錄了所有超過設定值的慢查詢,如何統計分析呢?總不能一條一條數。
MySQL提供了mysqldumpslow的工具,在MySQL的bin目錄下。
例如:查詢用時最多的20條慢SQL:
mysqldumpslow -s t -t 20 -g 'select' /var/lib/mysql/localhost-slow.log
Count:代表這條SQL被執行了多少次;
Time:代表執行的時間,括號內是累計時間;
Lock:代表鎖定的時間,括號是累計鎖定時間;
Rows:代表返回的記錄數,括號是累計;
4.2 show profile
除了慢查詢日誌,還有show profile工具可以使用
https://dev.mysql.com/doc/refman/5.7/en/show-profile.html
show profile可以查看SQL語句執行的時使用的資源,比如CPU、IO的消耗情況。
查看是否開啓:
select @@profiling;
若未開啓則手動開啓:
set @@profiling=1;
查看 profile 統計
show profiles;(命令最後帶一個 s)
查看最後一個 SQL 的執行詳細信息,從中找出耗時較多的環節(沒有 s)。
show profile;
此處時間6.2E-5表示小數點左移 5 位,代表 0.000062 秒。
也可以根據 ID 查看執行詳細信息,在後面帶上 for query + ID。
show profile for query 1;
除了慢日誌和 show profile,如果要分析出當前數據庫中執行的慢的 SQL,還可以
通過查看運行線程狀態和服務器運行信息、存儲引擎信息來分析。
其他系統命令
show processlist 運行線程
show processlist;
用於顯示用戶運行線程。可以根據 id 號 kill 線程。
也可以查表,效果一樣:
select * from information_schema.processlist;
Id : 線程的唯一標誌,可以根據它 kill 線程
User : 啓動這個線程的用戶,普通用戶只能看到自己的線程
db : 操作的數據庫
show status 服務器運行狀態
SHOW STATUS 用於查看 MySQL 服務器運行狀態(重啓後會清空),有 session 和 global 兩種作用域,格式:參數-值。
可以用 like 帶通配符過濾。
SHOW GLOBAL STATUS LIKE 'com_select'; -- 查看 select 次數
show engine 存儲引擎運行信息
show engine 用來顯示存儲引擎的當前運行信息,包括事務持有的表鎖、行鎖信息;
事務的鎖等待情況;線程信號量等待;文件 IO 請求;buffer pool 統計信息。
例如:
show engine innodb status;
如果需要將監控信息輸出到錯誤信息 error log 中(15 秒鐘一次),可以開啓輸出。
show variables like 'innodb_status_output%';
-- 開啓輸出:
SET GLOBAL innodb_status_output=ON;
SET GLOBAL innodb_status_output_locks=ON;
其實很多開源的慢查詢日誌監控工具,他們的原理其實也都是讀取的系統的變量和狀態。
那麼,現在我們已經知道哪些 SQL 慢了,爲什麼慢呢?慢在哪裏?
MySQL 提供了一個執行計劃的工具,
通過 EXPLAIN 我們可以模擬優化器執行 SQL 查詢語句的過程,來知道 MySQL 是
怎麼處理一條 SQL 語句的。通過這種方式我們可以分析語句或者表的性能瓶頸。
4.3 EXPLAIN 執行計劃
先創建三張表。一張課程表,一張老師表。
先創建如下三張表以供測試。三張表沒有任何索引。
CREATE TABLE `course` (
`cid` int(3) DEFAULT NULL,
`cname` varchar(20) DEFAULT NULL,
`tid` int(3) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `teacher` (
`tid` int(3) DEFAULT NULL,
`tname` varchar(20) DEFAULT NULL,
`tcid` int(3) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `teacher_contact` (
`tcid` int(3) DEFAULT NULL,
`phone` varchar(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1)id
執行計劃中id不同
EXPLAIN SELECT tc.phone FROM teacher_contact tc
WHERE tcid = (
SELECT tcid FROM teacher t WHERE t.tid = (
SELECT c.tid FROM course c WHERE c.cname = 'java'
)
);
-- 查詢 java 課程的老師手機號
SQL執行結果:
可以看到執行計劃中的id值不同,在id 值不同的時候,會先查詢 id 值大的(先大後小)。
查詢順序:course c——teacher t——teacher_contact tc。
子查詢只能以這種方式進行,只有拿到內層的結果之後才能進行外層的查詢。
執行計劃中id相同
EXPLAIN SELECT
t.tname,
c.cname,
tc.phone
FROM
teacher t,
course c,
teacher_contact tc
WHERE
t.tid = c.tid
AND t.tcid = tc.tcid
AND (c.cid = 2 OR tc.tcid = 3);
-- 查詢課程 ID 爲 2,或者聯繫表 ID 爲 3 的老師
id 值相同時,表的查詢順序是從上往下順序執行。
例如這次查詢的 id 都是 1,查詢的順序是 teacher t(3 條)——teacher_contact tc(3 條)——course c(4 條)。
嘗試對 teacher 表插入 3 條數據後:
INSERT INTO `teacher`
VALUES
(4, 'John', 4);
INSERT INTO `teacher`
VALUES
(5, 'Tyler', 5);
INSERT INTO `teacher`
VALUES
(6, 'David', 6);
COMMIT;
id 也都是 1,但是從上往下查詢順序變成了:teacher_contact tc(3 條)——course c(4 條)——teacher t(6 條)。
注意,爲什麼和插入三條數據前的執行順序不同了?
這是mysql因爲對笛卡爾積的處理。
假如有 a、b、c 三張表,分別有 2、3、4 條數據,如果做三張表的聯合查詢,
當查詢順序是 a→b→c 的時候,它的笛卡爾積是:2*3*4 = 6*4 = 24。
如果查詢順序是 c→b→a,它的笛卡爾積是 4*3*2 = 12*2 = 24。
因爲 MySQL 要把查詢的結果,包括中間結果和最終結果都保存到內存,
所以 MySQL會優先選擇中間結果數據量比較小的順序進行查詢。
所以最終聯表查詢的順序是 a→b→ c。
這就是插入數據影響的執行順序的原因。(小表驅動大表的思想)
既有相同也有不同
如果 ID 有相同也有不同,就是 ID 不同的先大後小,ID 相同的從上往下。
2)select type 查詢類型
SIMPLE :簡單查詢,不包含子查詢,不包含關聯查詢 union。
PRIMARY
子查詢 SQL 語句中的主查詢,也就是最外面的那層查詢。
SUBQUERY
子查詢中所有的內層查詢都是 SUBQUERY 類型的。
DERIVED
衍生查詢,表示在得到最終查詢結果之前會用到臨時表。
UNION
用到了 UNION 查詢。
UNION RESULT
主要是顯示哪些表之間存在 UNION 查詢。<union2,3>代表 id=2 和 id=3 的查詢存在 UNION。
3)type 連接類型
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain-join-types
在常用的鏈接類型中(效率高低排序):system > const > eq_ref > ref > range > index > all
這 裏 並 沒 有 列 舉 全 部 ( 其 他 : fulltext 、 ref_or_null 、 index_merger 、unique_subquery、index_subquery)。
除了all,都能用到索引。
const
條件爲主鍵索引或者唯一索引,且只能查到一條數據的 SQL。
system 是 const 的一種特例,只有一行滿足條件。例如:只有一條數據的系統表。不過在我們開發應用時基本不會去查系統表。
eq_ref
通常出現在多表的 join 查詢,表示對於前表的每一個結果,,都只能匹配到後表的一行結果。
一般是唯一性索引的查詢(UNIQUE 或 PRIMARY KEY)。
eq_ref 是除 const 之外最好的訪問類型。
DELETE FROM teacher where tid in (4,5,6);
commit; -- 刪除多餘的三條記錄
ALTER TABLE teacher_contact ADD PRIMARY KEY(tcid);
-- 爲 teacher_contact 表的 tcid 創建主鍵索引。
ALTER TABLE teacher ADD INDEX idx_tcid (tcid);
-- 爲 teacher 表的 tcid 創建普通索引。
以上三種 system,const,eq_ref,都是可遇而不可求的,基本上很難優化到這個狀態。
ref
查詢用到了非唯一性索引,或者關聯操作只使用了索引的最左前綴。
例如:使用 tcid 上的普通索引查詢:
range
索引範圍掃描。
如果 where 後面是條件是索引,且範圍爲 between and 或 <或 > 或 >= 或 <=或 in 這些,type 類型就爲 range。
注意 沒有索引type就是ALL 全表掃描了。
index
Full Index Scan,查詢全部索引中的數據(比不走索引要快)。
all
Full Table Scan,如果沒有索引或者沒有用到索引,type 就是 ALL。代表全表掃描。
NULL
不用訪問表或者索引就能得到結果,例如:
EXPLAIN select 1 from dual where 1=1;
小結:
一般來說,需要保證查詢至少達到 range 級別,最好能達到 ref。
ALL(全表掃描)和 index(查詢全部索引)都是需要優化的。
4) possible_key、key
possible_key : 可能用到的索引
key : 實際用到的索引
如果是 NULL 就代表沒有用到索引。
possible_key 可以有一個或者多個,可能用到索引並不代表一定用到索引。
possible_key 爲空,key 可能有值嗎?
ALTER TABLE user_innodb add INDEX comidx_name_phone (name,phone);--爲name和phone建立聯合索引
explain select phone from user_innodb where phone='126'; --以索引爲條件查詢建立了索引的字段
因此,是有可能的,這是使用到了覆蓋索引的情況(無需回表)。
如果優化時,通過分析發現沒有用到索引,就要檢查 SQL 或者創建索引。
5)key_len
索引的長度(使用的字節數)。跟索引字段的類型、長度有關。
6)rows
MySQL 認爲掃描多少行才能返回請求的數據,是一個預估值。一般來說行數越少越好。
7)filtered
這個字段表示存儲引擎返回的數據在 server 層過濾後,剩下多少滿足查詢的記錄數量的比例,它是一個百分比。
8)ref
使用哪個列或者常數和索引一起從表中篩選數據。
9)Extra
執行計劃給出的額外的信息說明。
using index:
用到了覆蓋索引,不需要回表。
using where:
使用了 where 過濾,表示存儲引擎返回的記錄並不是所有的都滿足查詢條件,
需要在 server 層進行過濾(跟是否使用索引沒有關係)。
Using index condition(索引條件下推):使用到了第二篇中提到的索引條件下推
using filesort:不能使用索引來排序,用到了額外的排序(跟磁盤或文件沒有關係)。需要優化。
using temporary :用到了臨時表。
比如:
distinct 非索引列:
EXPLAIN select DISTINCT(tid) from teacher t;
group by 非索引列:
EXPLAIN select tname from teacher group by tname;
使用 join 的時候,group 任意列:
EXPLAIN select t.tid from teacher t join course c on t.tid = c.tid group by t.tid;
總結一下,模擬優化器執行 SQL 查詢語句的過程,來知道 MySQL 是怎麼處理一條 SQL 語句的。
通過這種方式我們可以分析語句或者表的性能瓶頸。
分析出問題之後,就是對 SQL 語句進行鍼對性的具體優化。
五、存儲引擎
5.1 存儲引擎的選擇
爲不同的業務表選擇不同的存儲引擎。比如查詢插入操作多或事物一致性要求低的用MyISAM,臨時數據用Memory,常規的併發大更新多的表用InnoDB。
5.2 分區或者分表
分區不推薦。
交易歷史表:在年底爲下一年度建立12個分區,每個月一個分區。
渠道交易表:分成當日表;當月表;歷史表,歷史表再做分區。
5.3 字段定義
原則:使用可以正確存儲數據的最小數據類型,爲每一列選擇合適的字段類型。
5.3.1 整數類型
INT有8種類型,不同類型的最大存儲範圍是不一樣的。
性別?用TINYINT,因爲ENUM也是整形存儲。
5.3.2 字符類型
變長情況下,varchar更節省時間,但是對於varchar字段,需要一個字節來記錄長度。
固定長度的用char,不要用varchar。
5.3.3 非空
非空字段儘量定義成NOT NULL,提供默認值,或者使用特殊值、控制代替null。
NULL類型的存儲、優化、使用都會存在問題。
5.3.4 不要用外鍵、觸發器、視圖
降低了可讀性;
影響數據庫性能,應該把計算的事交給程序,數據庫只做存儲;
數據的完整性應該在程序中檢查。
5.3.5 大文件存儲
不要用數據庫存儲圖片(比如base64)或者大文件;
把文件放在NAS上,數據庫只存儲URI,在應用中配置NAS服務器地址。
5.3.6 表拆分
將不常用的字段拆分出去,避免列數過多和數據量過大。
比如在業務系統中,要記錄所有接收和發送的消息,這個消息是XML格式的,用blob或者text存儲,用來追蹤和判斷重複,可以建立一張表專門用來存儲報文。
六、總結:優化體系
如圖,對於優化的方向,優化難度從上到下是依次增加的,但是優化得到的效益卻不一定。
因此我們優化的方向選擇儘量從上而下開始。
除了對於代碼、SQL 語句、表定義、架構、配置優化之外,業務層面的優化也不能忽視。舉幾個例子:
1)雙十一時,爲什麼在凌晨精緻查詢今天之外的賬單?
這是一種降級措施,用來保證當前最核心的業務。
2)爲什麼在雙十一之前,提前一個多星期就已經有雙十一的預售價格了?
這是通過預售的手段實現了分流。
在應用層面同樣有很多其他的方案來優化,達到儘量減輕數據庫的壓力的目的,比
如限流,或者引入 MQ 削峯,等等。