MySQL性能分析與優(yōu)化

mysql官網(wǎng):https://dev.mysql.com/doc/refman/5.6/en/server-status-variables.html

一、查詢(xún)執(zhí)行過(guò)程

二、優(yōu)化思路

數(shù)據(jù)查詢(xún)慢,不代表sql語(yǔ)句寫(xiě)法有問(wèn)題,用一張流程圖展示MySQL優(yōu)化思路

主要包括:
1、適當(dāng)添加索引(四種:普通索引、主鍵索引、唯一索引、全文索引)
2、分表技術(shù)(水平分割、垂直分割)
3、讀寫(xiě)(寫(xiě):update/delete/add)分離
4、對(duì)mysql配置優(yōu)化(my.conf)
5、mysql服務(wù)器硬件升級(jí)

2.1mysql查詢(xún)執(zhí)行順序

示例SQL:

SELECT * 
FROM user 
LEFT JOIN order ON user.id = order.uid 
WHERE order.price > 1000 
GROUP BY user.name 
HAVING count(1) > 5 
ORDER BY user.name
LIMIT 0,10

1、FROM(將最近的兩張表,進(jìn)行笛卡爾積)—VT1
2、ON(將VT1按照它的條件進(jìn)行過(guò)濾)—VT2
3、LEFT JOIN(保留左表的記錄)—VT3
4、WHERE(過(guò)濾VT3中的記錄)–VT4…VTn
5、GROUP BY(對(duì)VT4的記錄進(jìn)行分組)—VT5
6、HAVING(對(duì)VT5中的記錄進(jìn)行過(guò)濾)—VT6
7、SELECT(對(duì)VT6中的記錄,選取指定的列)–VT7
8、ORDER BY(對(duì)VT7的記錄進(jìn)行排序)–游標(biāo)
9、LIMIT(對(duì)排序之后的值進(jìn)行分頁(yè))
WHERE條件執(zhí)行順序(影響性能)
1、MYSQL:從左往右去執(zhí)行WHERE條件的。
2、Oracle:從右往左去執(zhí)行WHERE條件的。
結(jié)論:寫(xiě)WHERE條件的時(shí)候,優(yōu)先級(jí)高的部分要去編寫(xiě)過(guò)濾力度最大的條件語(yǔ)句。

三、查看MySQL服務(wù)器運(yùn)行的狀態(tài)值/服務(wù)器配置信息

3.1 查詢(xún)MySQL 服務(wù)器運(yùn)行的狀態(tài)值

如果系統(tǒng)的并發(fā)請(qǐng)求數(shù)不高,且查詢(xún)速度慢,可以忽略該步驟直接進(jìn)行 SQL 語(yǔ)句調(diào)優(yōu)步驟。
執(zhí)行命令:show status/show global status 可以查看所有的性能會(huì)話(huà)參數(shù)/全局參數(shù);或 show status like '參數(shù)名稱(chēng)' 可以查看指定參數(shù)名稱(chēng)的性能參數(shù),一般某一類(lèi)參數(shù)都有相同的前綴。
返回結(jié)果太多,我們主要關(guān)注“Queries”、“Threadsconnected”“Threadsrunning” 的值,即查詢(xún)次數(shù)、線(xiàn)程連接數(shù)和線(xiàn)程運(yùn)行數(shù)。

3.2 查詢(xún)MySQL服務(wù)器配置信息

執(zhí)行命令:show variables/show global variables

3.3 status 與 variables 區(qū)別

status查看的參數(shù)值是由MySQL自己統(tǒng)計(jì)計(jì)算得到的。它是MySQL服務(wù)運(yùn)行狀態(tài)具體的量化體現(xiàn)。都是不可以修改的,也就是不能通過(guò)setxxx=yyy;的方式來(lái)改變它的值的。參數(shù)大多數(shù)以大寫(xiě)的英文字母開(kāi)頭。
variables查看的參數(shù)是MySQL服務(wù)的配置參數(shù),在啟動(dòng)MySQL服務(wù)的時(shí)候,是可以修改具體的參數(shù)值來(lái)達(dá)到對(duì)MySQL進(jìn)行動(dòng)態(tài)配置的目的,通常配置在MySQL的my.cnf配置文件中。這些參數(shù)中,有些動(dòng)態(tài)的參數(shù)可以通過(guò)setxxx=yyy;的方式來(lái)動(dòng)態(tài)修改。這種參數(shù)大多數(shù)以小寫(xiě)的英文字母開(kāi)頭

3.4 什么是全局參數(shù)?什么是會(huì)話(huà)session級(jí)別的參數(shù)?

全局參數(shù):某個(gè)參數(shù)的值設(shè)置之后,對(duì)所有連接到MySQL服務(wù)的用戶(hù)都生效,所有用戶(hù)登錄到MySQL后,該參數(shù)都是使用同一個(gè)值。
會(huì)話(huà)級(jí)別的參數(shù):某個(gè)參數(shù)的值設(shè)置之后,只對(duì)當(dāng)前連接到MySQL服務(wù)器的當(dāng)前會(huì)話(huà)生效,當(dāng)前會(huì)話(huà)退出后,這個(gè)參數(shù)就恢復(fù)了默認(rèn)值。并且當(dāng)連接A登錄的時(shí)候,這個(gè)參數(shù)對(duì)A生效。但是對(duì)于新開(kāi)一個(gè)連接服務(wù)B,在B的會(huì)話(huà)中,設(shè)置的參數(shù)并不會(huì)生效。

3.5 核心參數(shù)解析
3.5.1 慢查詢(xún)
mysql> show variables like '%slow%';
+——————+——-+
| Variable_name          | Value |
+——————+——-+
| log_slow_queries          | ON   |
| slow_launch_time          | 2   |
+——————+——-+
mysql> show global status like '%slow%';
+———————+——-+
| Variable_name        | Value |
+———————+——-+
| Slow_launch_threads        | 0   |
| Slow_queries        | 4148 |
+———————+——-+

配置中打開(kāi)了記錄慢查詢(xún),執(zhí)行時(shí)間超過(guò)2秒的即為慢查詢(xún),系統(tǒng)顯示有4148個(gè)慢查詢(xún),你可以分析慢查詢(xún)?nèi)罩?,找出有?wèn)題的SQL語(yǔ)句,慢查詢(xún)時(shí)間不宜設(shè)置過(guò)長(zhǎng),否則意義不大,最好在5秒以?xún)?nèi),如果你需要微秒級(jí)別的慢查詢(xún),可以考慮給MySQL打補(bǔ)丁:http://www.percona.com/docs/wiki/release:start,記得找對(duì)應(yīng)的版本。
打開(kāi)慢查詢(xún)?nèi)罩究赡軙?huì)對(duì)系統(tǒng)性能有一點(diǎn)點(diǎn)影響,如果你的MySQL是主-從結(jié)構(gòu),可以考慮打開(kāi)其中一臺(tái)從服務(wù)器的慢查詢(xún)?nèi)罩荆@樣既可以監(jiān)控慢查詢(xún),對(duì)系統(tǒng)性能影響又小。

3.5.2 連接數(shù)

經(jīng)常會(huì)遇見(jiàn)”MySQL: ERROR 1040: Too many connections”的情況,一種是訪(fǎng)問(wèn)量確實(shí)很高,MySQL服務(wù)器抗不住,這個(gè)時(shí)候就要考慮增加從服務(wù)器分散讀壓力,另外一種情況是MySQL配置文件中max_connections值過(guò)小:

mysql> show variables like 'max_connections';
+—————–+——-+
| Variable_name    | Value |
+—————–+——-+
| max_connections    | 256  |
+—————–+——-+

這臺(tái)MySQL服務(wù)器最大連接數(shù)是256,然后查詢(xún)一下服務(wù)器響應(yīng)的最大連接數(shù):

mysql> show global status like 'Max_used_connections';

MySQL服務(wù)器過(guò)去的最大連接數(shù)是245,沒(méi)有達(dá)到服務(wù)器連接數(shù)上限256,應(yīng)該沒(méi)有出現(xiàn)1040錯(cuò)誤,比較理想的設(shè)置是
Max_used_connections / max_connections * 100% ≈ 85%
最大連接數(shù)占上限連接數(shù)的85%左右,如果發(fā)現(xiàn)比例在10%以下,MySQL服務(wù)器連接數(shù)上限設(shè)置的過(guò)高了。

3.5.3 進(jìn)程使用情況
mysql> show global status like 'Thread%';
+——————-+——-+
| Variable_name      | Value |
+——————-+——-+
| Threads_cached      | 46   |
| Threads_connected      | 2    |
| Threads_created      | 570   |
| Threads_running      | 1     |
+——————-+——-+

如果我們?cè)贛ySQL服務(wù)器配置文件中設(shè)置了thread_cache_size,當(dāng)客戶(hù)端斷開(kāi)之后,服務(wù)器處理此客戶(hù)的線(xiàn)程將會(huì)緩存起來(lái)以響應(yīng)下一個(gè)客戶(hù)而不是銷(xiāo)毀(前提是緩存數(shù)未達(dá)上限)。Threads_created表示創(chuàng)建過(guò)的線(xiàn)程數(shù),如果發(fā)現(xiàn)Threads_created值過(guò)大的話(huà),表明MySQL服務(wù)器一直在創(chuàng)建線(xiàn)程,這也是比較耗資源,可以適當(dāng)增加配置文件中thread_cache_size值,查詢(xún)服務(wù)器thread_cache_size配置:

mysql> show variables like 'thread_cache_size';
+——————-+——-+
| Variable_name      | Value |
+——————-+——-+
| thread_cache_size      | 64   |
+——————-+——-+

四、獲取需要優(yōu)化的SQL語(yǔ)句

4.1 方式一:查看運(yùn)行的線(xiàn)程

執(zhí)行命令:show processlist / show full processlist
返回結(jié)果:

從返回結(jié)果中我們可以了解該線(xiàn)程執(zhí)行了什么命令/SQL 語(yǔ)句以及執(zhí)行的時(shí)間。實(shí)際應(yīng)用中,查詢(xún)的返回結(jié)果會(huì)有 N 條記錄。
其中,返回的 State 的值是我們判斷性能好壞的關(guān)鍵,其值出現(xiàn)如下內(nèi)容,則該行記錄的 SQL 語(yǔ)句需要優(yōu)化:

  • Converting HEAP to MyISAM # 查詢(xún)結(jié)果太大時(shí),把結(jié)果放到磁盤(pán),嚴(yán)重
  • Create tmp table #創(chuàng)建臨時(shí)表,嚴(yán)重
  • Copying to tmp table on disk #把內(nèi)存臨時(shí)表復(fù)制到磁盤(pán),嚴(yán)重
  • locked #被其他查詢(xún)鎖住,嚴(yán)重
  • loggin slow query #記錄慢查詢(xún)
  • Sorting result #排序
4.2 方式二:開(kāi)啟慢查詢(xún)?nèi)罩荆ㄍ扑])

在配置文件 my.cnf 中的 [mysqld] 一行下邊添加幾個(gè)參數(shù):

slow_query_log = 1 
slow_query_log_file=/var/lib/mysql/slow.log 
long_query_time = 2 
log_queries_not_using_indexes = 1

其中,slowquerylog = 1 表示開(kāi)啟慢查詢(xún);slowquerylogfile 表示慢查詢(xún)?nèi)罩敬娣诺奈恢?;longquerytime = 2 表示查詢(xún) >=2 秒才記錄日志;logqueriesnotusing_indexes = 1 記錄沒(méi)有使用索引的 SQL 語(yǔ)句。
注意:slowquerylog_file 的路徑不能隨便寫(xiě),否則 MySQL 服務(wù)器可能沒(méi)有權(quán)限將日志文件寫(xiě)到指定的目錄中。
修改保存文件后,重啟 MySQL 服務(wù)。在 /var/lib/mysql/ 目錄下會(huì)創(chuàng)建 slow-query.log 日志文件。連接 MySQL 服務(wù)端執(zhí)行如下命令可以查看配置情況。

show variables like 'slow_query%'; 
show variables like 'long_query_time';

測(cè)試慢查詢(xún)?nèi)罩荆?/p>

打開(kāi)慢查詢(xún)?nèi)罩疚募?/p>

我們可以看到剛才執(zhí)行了 2 秒的 SQL 語(yǔ)句被記錄下來(lái)了。
雖然在慢查詢(xún)?nèi)罩局杏涗洸樵?xún)慢的 SQL 信息,但是日志記錄的內(nèi)容密集且不易查閱。因此,我們需要通過(guò)工具將 SQL 篩選出來(lái)。
MySQL 提供 mysqldumpslow 工具對(duì)日志進(jìn)行分析。我們可以使用 mysqldumpslow --help 查看命令相關(guān)用法。
常用參數(shù)如下:

案例:

獲取返回記錄集最多的10個(gè)sql 
mysqldumpslow -s r -t 10 /var/lib/mysql/slow-query.log 

獲取訪(fǎng)問(wèn)次數(shù)最多的10個(gè)sql 
mysqldumpslow -s c -t 10 /var/lib/mysql/slow-query.log 

獲取按照時(shí)間排序的前10條里面含有左連接的查詢(xún)語(yǔ)句 
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/slow-query.log

五、分析SQL語(yǔ)句

5.1 方式一:explain

篩選出有問(wèn)題的 SQL,我們可以使用 MySQL 提供的 explain 查看 SQL 執(zhí)行計(jì)劃情況(關(guān)聯(lián)表,表查詢(xún)順序、索引使用情況等)。
用法:explain select * from user;
返回結(jié)果:

 +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
 | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
 +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
 | 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL | 
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+ 
1 row in set, 1 warning (0.00 sec)

注意:在5.7以前的版本中,想要顯示partitions需要使用explain partitions命令;想要顯示filtered需要使用explain extended命令。在5.7版本后,默認(rèn)explain直接顯示partitions和filtered中的信息。
字段解釋?zhuān)?/p>

  1. id:select 查詢(xún)序列號(hào)。id相同,執(zhí)行順序由上至下;id不同,id值越大優(yōu)先級(jí)越高,越先被執(zhí)行
  2. select_type:查詢(xún)數(shù)據(jù)的操作類(lèi)型,其值如下:
  • simple:簡(jiǎn)單查詢(xún),不包含子查詢(xún)或 union
  • primary:包含復(fù)雜的子查詢(xún),最外層查詢(xún)標(biāo)記為該值
  • subquery:在 select 或 where 包含子查詢(xún),被標(biāo)記為該值
  • derived:在 from 列表中包含的子查詢(xún)被標(biāo)記為該值,MySQL 會(huì)遞歸執(zhí)行這些子查詢(xún),把結(jié)果放在臨時(shí)表
  • union:若第二個(gè) select 出現(xiàn)在 union 之后,則被標(biāo)記為該值。若 union 包含在 from 的子查詢(xún)中,外層 select 被標(biāo)記為 derived
  • union result:從 union 表獲取結(jié)果的 select
  1. table:顯示該行數(shù)據(jù)是關(guān)于哪張表
  2. partitions:匹配的分區(qū)
  3. type:表的連接類(lèi)型,其值,性能由高到底排列如下:
  • system:表只有一行記錄,相當(dāng)于系統(tǒng)表
  • const:通過(guò)索引一次就找到,只匹配一行數(shù)據(jù)
  • eq_ref:唯一性索引掃描,對(duì)于每個(gè)索引鍵,表中只有一條記錄與之匹配。常用于主鍵或唯一索引掃描
  • ref:非唯一性索引掃描,返回匹配某個(gè)單獨(dú)值的所有行。用于=、< 或 > 操作符帶索引的列
  • range:只檢索給定范圍的行,使用一個(gè)索引來(lái)選擇行。一般使用between、>、<情況
  • index:只遍歷索引樹(shù)
  • ALL:全表掃描,性能最差
    注:前5種情況都是理想情況的索引使用情況。通常優(yōu)化至少到range級(jí)別,最好能優(yōu)化到 ref
  1. possible_keys:指出 MySQL 使用哪個(gè)索引在該表找到行記錄。如果該值為 NULL,說(shuō)明沒(méi)有使用索引,可以建立索引提高性能
  2. key:顯示 MySQL 實(shí)際使用的索引。如果為 NULL,則沒(méi)有使用索引查詢(xún)
  3. key_len:表示索引中使用的字節(jié)數(shù),通過(guò)該列計(jì)算查詢(xún)中使用的索引的長(zhǎng)度。在不損失精確性的情況下,長(zhǎng)度越短越好 顯示的是索引字段的最大長(zhǎng)度,并非實(shí)際使用長(zhǎng)度
  4. ref:顯示該表的索引字段關(guān)聯(lián)了哪張表的哪個(gè)字段
  5. rows:根據(jù)表統(tǒng)計(jì)信息及選用情況,大致估算出找到所需的記錄或所需讀取的行數(shù),數(shù)值越小越好
  6. filtered:返回結(jié)果的行數(shù)占讀取行數(shù)的百分比,值越大越好
  7. extra:包含不合適在其他列中顯示但十分重要的額外信息,常見(jiàn)的值如下:
  • using filesort:說(shuō)明 MySQL 會(huì)對(duì)數(shù)據(jù)使用一個(gè)外部的索引排序,而不是按照表內(nèi)的索引順序進(jìn)行讀取。出現(xiàn)該值,應(yīng)該優(yōu)化 SQL
  • using temporary:使用了臨時(shí)表保存中間結(jié)果,MySQL 在對(duì)查詢(xún)結(jié)果排序時(shí)使用臨時(shí)表。常見(jiàn)于排序 order by 和分組查詢(xún) group by。出現(xiàn)該值,應(yīng)該優(yōu)化 SQL
  • using index:表示相應(yīng)的 select 操作使用了覆蓋索引,避免了訪(fǎng)問(wèn)表的數(shù)據(jù)行,效率不錯(cuò)
  • using where:where 子句用于限制哪一行
  • using join buffer:使用連接緩存
  • distinct:發(fā)現(xiàn)第一個(gè)匹配后,停止為當(dāng)前的行組合搜索更多的行
    注意:出現(xiàn)前 2 個(gè)值,SQL 語(yǔ)句必須要優(yōu)化。
5.2 方式二:profiling

使用 profiling 命令可以了解 SQL 語(yǔ)句消耗資源的詳細(xì)信息(每個(gè)執(zhí)行步驟的開(kāi)銷(xiāo))。

5.2.1 查看 profile 開(kāi)啟情況

執(zhí)行命令:select @@profiling;
返回結(jié)果:

0 表示關(guān)閉狀態(tài),1 表示開(kāi)啟

5.2.2 啟用 profile

執(zhí)行命令:set profiling = 1;
返回結(jié)果:

在連接關(guān)閉后,profiling 狀態(tài)自動(dòng)設(shè)置為關(guān)閉狀態(tài)。

5.2.3 查看執(zhí)行的 SQL 列表

執(zhí)行命令:show profiles;
返回結(jié)果:

該命令執(zhí)行之前,需要執(zhí)行其他 SQL 語(yǔ)句才有記錄。

5.2.4 查詢(xún)指定 ID 的執(zhí)行詳細(xì)信息

執(zhí)行命令:show profile for query Query_ID;
返回結(jié)果:

每行都是狀態(tài)變化的過(guò)程以及它們持續(xù)的時(shí)間。Status 這一列和 show processlist 的 State 是一致的。因此,需要優(yōu)化的注意點(diǎn)與上文描述的一樣。

六、優(yōu)化手段

6.1 查詢(xún)優(yōu)化
  1. 避免 SELECT *,需要什么數(shù)據(jù),就查詢(xún)對(duì)應(yīng)的字段。
  2. 小表驅(qū)動(dòng)大表,即小的數(shù)據(jù)集驅(qū)動(dòng)大的數(shù)據(jù)集。如:以 A,B 兩表為例,兩表通過(guò) id 字段進(jìn)行關(guān)聯(lián)。
    當(dāng) B 表的數(shù)據(jù)集小于 A 表時(shí),用 in 優(yōu)化 exist;使用 in ,兩表執(zhí)行順序是先查 B 表,再查 A 表
select * from A where id in (select id from B)

當(dāng) A 表的數(shù)據(jù)集小于 B 表時(shí),用 exist 優(yōu)化 in;使用 exists,兩表執(zhí)行順序是先查 A 表,再查 B 表

select * from A where exists (select 1 from B where B.id = A.id)
  1. 一些情況下,可以使用連接代替子查詢(xún),因?yàn)槭褂?join,MySQL 不會(huì)在內(nèi)存中創(chuàng)建臨時(shí)表。
  2. 適當(dāng)添加冗余字段,減少表關(guān)聯(lián)。
  3. 合理使用索引。如:為排序、分組字段建立索引,避免 filesort 的出現(xiàn)。
6.2 索引使用
6.2.1 適合使用索引的場(chǎng)景
  1. 主鍵自動(dòng)創(chuàng)建唯一索引
  2. 頻繁作為查詢(xún)條件的字段
  3. 查詢(xún)中與其他表關(guān)聯(lián)的字段
  4. 查詢(xún)中排序的字段
  5. 查詢(xún)中統(tǒng)計(jì)或分組字段
6.2.2 不適合使用索引的場(chǎng)景
  1. 頻繁更新的字段
  2. where 條件中用不到的字段
  3. 表記錄太少
  4. 經(jīng)常增刪改的表
  5. 字段的值的差異性不大或重復(fù)性高
6.2.3 索引創(chuàng)建和使用原則
  1. 單表查詢(xún):哪個(gè)列作查詢(xún)條件,就在該列創(chuàng)建索引
  2. 多表查詢(xún):left join 時(shí),索引添加到右表關(guān)聯(lián)字段;right join 時(shí),索引添加到左表關(guān)聯(lián)字段
  3. 不要對(duì)索引列進(jìn)行任何操作(計(jì)算、函數(shù)、類(lèi)型轉(zhuǎn)換)
  4. 索引列中不要使用 !=,<> 非等于
  5. 索引列不要為空,且不要使用 is null 或 is not null 判斷
  6. 索引字段是字符串類(lèi)型,查詢(xún)條件的值要加''單引號(hào),避免底層類(lèi)型自動(dòng)轉(zhuǎn)換
  7. 違背上述原則可能會(huì)導(dǎo)致索引失效,具體情況需要使用 explain 命令進(jìn)行查看
6.2.4 索引失效情況

除了違背索引創(chuàng)建和使用原則外,如下情況也會(huì)導(dǎo)致索引失效:

  1. 模糊查詢(xún)時(shí),以 % 開(kāi)頭
  2. 使用 or 時(shí),如:字段1(非索引)or 字段2(索引)會(huì)導(dǎo)致索引失效。
  3. 使用復(fù)合索引時(shí),不使用第一個(gè)索引列。index(a,b,c) ,以字段 a,b,c 作為復(fù)合索引為例:

注意:檢索字段加了索引,但是范圍查詢(xún)時(shí)mysql優(yōu)化器不一定會(huì)使用該索引,因?yàn)槿绻樵?xún)的數(shù)據(jù)較多,優(yōu)化器會(huì)選擇走全表掃描

6.3 數(shù)據(jù)庫(kù)表結(jié)構(gòu)設(shè)計(jì)
6.3.1 選擇合適的數(shù)據(jù)類(lèi)型

使用可以存下數(shù)據(jù)最小的數(shù)據(jù)類(lèi)型

  1. 使用簡(jiǎn)單的數(shù)據(jù)類(lèi)型。int 要比 varchar 類(lèi)型在mysql處理簡(jiǎn)單
  2. 盡量使用 tinyint、smallint、mediumint 作為整數(shù)類(lèi)型而非 int
  3. 盡可能使用 not null 定義字段,因?yàn)?null 占用4字節(jié)空間
  4. 盡量少用 text 類(lèi)型,非用不可時(shí)最好考慮分表
  5. 盡量使用 timestamp 而非 datetime
  6. 單表不要有太多字段,建議在 20 以?xún)?nèi)
6.3.2 表的拆分

當(dāng)數(shù)據(jù)庫(kù)中的數(shù)據(jù)非常大時(shí),查詢(xún)優(yōu)化方案也不能解決查詢(xún)速度慢的問(wèn)題時(shí),我們可以考慮拆分表,讓每張表的數(shù)據(jù)量變小,從而提高查詢(xún)效率。

  1. 垂直拆分:將表中多個(gè)列分開(kāi)放到不同的表中。例如用戶(hù)表中一些字段經(jīng)常被訪(fǎng)問(wèn),將這些字段放在一張表中,另外一些不常用的字段放在另一張表中。插入數(shù)據(jù)時(shí),使用事務(wù)確保兩張表的數(shù)據(jù)一致性。
  2. 水平拆分:按照行進(jìn)行拆分。例如用戶(hù)表中,使用用戶(hù)ID,對(duì)用戶(hù)ID取10的余數(shù),將用戶(hù)數(shù)據(jù)均勻的分配到0~9的10個(gè)用戶(hù)表中。查找時(shí)也按照這個(gè)規(guī)則查詢(xún)數(shù)據(jù)。
6.3.3 讀寫(xiě)分離

一般情況下對(duì)數(shù)據(jù)庫(kù)而言都是“讀多寫(xiě)少”。換言之,數(shù)據(jù)庫(kù)的壓力多數(shù)是因?yàn)榇罅康淖x取數(shù)據(jù)的操作造成的。我們可以采用數(shù)據(jù)庫(kù)集群的方案,使用一個(gè)庫(kù)作為主庫(kù),負(fù)責(zé)寫(xiě)入數(shù)據(jù);其他庫(kù)為從庫(kù),負(fù)責(zé)讀取數(shù)據(jù)。這樣可以緩解對(duì)數(shù)據(jù)庫(kù)的訪(fǎng)問(wèn)壓力。

6.4 補(bǔ)充
6.4.1 MySQL自帶壓力測(cè)試工具mysqlslap

常用參數(shù) [options] 詳細(xì)說(shuō)明:

-u:連接數(shù)據(jù)庫(kù)用戶(hù)名
-p:鏈接數(shù)據(jù)庫(kù)密碼
--auto-generate-sql, -a 自動(dòng)生成測(cè)試表和數(shù)據(jù),表示用mysqlslap工具自己生成的SQL腳本來(lái)測(cè)試并發(fā)壓力。
--auto-generate-sql-load-type=type 測(cè)試語(yǔ)句的類(lèi)型。代表要測(cè)試的環(huán)境是讀操作還是寫(xiě)操作還是兩者混合的。取值包括:read,key,write,update和mixed(默認(rèn))。
--auto-generate-sql-add-auto-increment 代表對(duì)生成的表自動(dòng)添加auto_increment列,從5.1.18版本開(kāi)始支持。
--number-char-cols=N, -x N 自動(dòng)生成的測(cè)試表中包含多少個(gè)字符類(lèi)型的列,默認(rèn)1
--number-int-cols=N, -y N 自動(dòng)生成的測(cè)試表中包含多少個(gè)數(shù)字類(lèi)型的列,默認(rèn)1
--number-of-queries=N 總的測(cè)試查詢(xún)次數(shù)(并發(fā)客戶(hù)數(shù)×每客戶(hù)查詢(xún)次數(shù))
--query=name,-q 使用自定義腳本執(zhí)行測(cè)試,例如可以調(diào)用自定義的一個(gè)存儲(chǔ)過(guò)程或者sql語(yǔ)句來(lái)執(zhí)行測(cè)試。
--create-schema 代表自定義的測(cè)試庫(kù)名稱(chēng),測(cè)試的schema,MySQL中schema也就是database。
--commint=N 多少條DML后提交一次。
--compress, -C 如果服務(wù)器和客戶(hù)端支持都?jí)嚎s,則壓縮信息傳遞。
--concurrency=N, -c N 表示并發(fā)量,也就是模擬多少個(gè)客戶(hù)端同時(shí)執(zhí)行select??芍付ǘ鄠€(gè)值,以逗號(hào)或者--delimiter參數(shù)指定的值做為分隔符。例如:--concurrency=100,200,500。
--engine=engine_name, -e engine_name 代表要測(cè)試的引擎,可以有多個(gè),用分隔符隔開(kāi)。例如:--engines=myisam,innodb。
--iterations=N, -i N 測(cè)試執(zhí)行的迭代次數(shù),代表要在不同并發(fā)環(huán)境下,各自運(yùn)行測(cè)試多少次。
--only-print 只打印測(cè)試語(yǔ)句而不實(shí)際執(zhí)行。
--detach=N 執(zhí)行N條語(yǔ)句后斷開(kāi)重連。
--debug-info, -T 打印內(nèi)存和CPU的相關(guān)信息。

說(shuō)明:測(cè)試的過(guò)程需要生成測(cè)試表,插入測(cè)試數(shù)據(jù),這個(gè)mysqlslap可以自動(dòng)生成,默認(rèn)生成一個(gè)mysqlslap的schema,如果已經(jīng)存在則先刪除??梢杂?-only-print來(lái)打印實(shí)際的測(cè)試過(guò)程,整個(gè)測(cè)試完成后不會(huì)在數(shù)據(jù)庫(kù)中留下痕跡。
各種測(cè)試參數(shù)實(shí)例(-p后面跟的是mysql的root密碼):

單線(xiàn)程測(cè)試。測(cè)試做了什么。
mysqlslap -a -uroot -p123456

多線(xiàn)程測(cè)試。使用–concurrency來(lái)模擬并發(fā)連接。
mysqlslap -a -c 100 -uroot -p123456

迭代測(cè)試。用于需要多次執(zhí)行測(cè)試得到平均值。
mysqlslap -a -i 10 -uroot -p123456
mysqlslap ---auto-generate-sql-add-autoincrement -a -uroot -p123456
mysqlslap -a --auto-generate-sql-load-type=read -uroot -p123456
mysqlslap -a --auto-generate-secondary-indexes=3 -uroot -p123456
mysqlslap -a --auto-generate-sql-write-number=1000 -uroot -p123456
mysqlslap --create-schema world -q "select count(*) from City" -uroot -p123456
mysqlslap -a -e innodb -uroot -p123456
mysqlslap -a --number-of-queries=10 -uroot -p123456

測(cè)試同時(shí)不同的存儲(chǔ)引擎的性能進(jìn)行對(duì)比:
mysqlslap -a --concurrency=50,100 --number-of-queries 1000 --iterations=5 --engine=myisam,innodb --debug-info -uroot -p123456

執(zhí)行一次測(cè)試,分別50和100個(gè)并發(fā),執(zhí)行1000次總查詢(xún):
mysqlslap -a --concurrency=50,100 --number-of-queries 1000 --debug-info -uroot -p123456

50和100個(gè)并發(fā)分別得到一次測(cè)試結(jié)果(Benchmark),并發(fā)數(shù)越多,執(zhí)行完所有查詢(xún)的時(shí)間越長(zhǎng)。為了準(zhǔn)確起見(jiàn),可以多迭代測(cè)試幾次:
mysqlslap -a --concurrency=50,100 --number-of-queries 1000 --iterations=5 --debug-info -uroot -p123456

七、補(bǔ)充

7.1 數(shù)據(jù)表設(shè)計(jì)建議

《阿里巴巴Java開(kāi)發(fā)手冊(cè)》,數(shù)據(jù)庫(kù)的表設(shè)計(jì)允許適當(dāng)冗余,以提升SQL查詢(xún)的性能,避免表的關(guān)聯(lián)查詢(xún)。

7.1.1 適度冗余,減少join的關(guān)聯(lián)

冗余更新頻率不高,但是查詢(xún)頻率極高的字段。如訂單中的商品名稱(chēng)。

7,1.2 大字段垂直拆分

比如商品SPU的商品的描述

7.1.3 大表水平拆分

比如優(yōu)惠券、帖子等

7.1.4 合適的數(shù)據(jù)類(lèi)型

如果數(shù)據(jù)量一樣,但數(shù)據(jù)類(lèi)型更小的話(huà),數(shù)據(jù)存放同樣的數(shù)據(jù)就會(huì)占用更少的空間,這樣檢索同樣的數(shù)據(jù)所帶來(lái)的IO消耗自然會(huì)降低,性能也就很自然的得到提升。此外,MySQL 對(duì)不同類(lèi)型的數(shù)據(jù),處理方式也不一樣,比如在運(yùn)算或者排序操作中,越簡(jiǎn)單的數(shù)據(jù)類(lèi)型操作性能越高,所以對(duì)于要頻繁進(jìn)行運(yùn)算或者排序的字段盡量選擇簡(jiǎn)單的數(shù)據(jù)類(lèi)型。

7.2 SQL優(yōu)化建議
7.2.1 聯(lián)合索引盡量覆蓋條件

比如可以設(shè)計(jì)一個(gè)或者兩三個(gè)聯(lián)合索引(盡量少建單值索引),讓每一個(gè)聯(lián)合索引都盡量去包含sql語(yǔ)句里的where、order by、group by的字段,還要確保這些聯(lián)合索引的字段順序盡量滿(mǎn)足sql查詢(xún)的最左前綴原則。

7.2.2 不要在小基數(shù)字段上建立索引

索引基數(shù)是指這個(gè)字段在表里總共有多少個(gè)不同的值,比如一張表總共100萬(wàn)行記錄,其中有個(gè)性別字段,其值不是男就是女,那么該字段的基數(shù)就是2。如果對(duì)這種小基數(shù)字段建立索引的話(huà),還不如全表掃描了,因?yàn)槟愕乃饕龢?shù)里就包含男和女兩種值,根本沒(méi)法進(jìn)行快速的二分查找,那用索引就沒(méi)有太大的意義了。一般建立索引,盡量使用那些基數(shù)比較大的字段,就是值比較多的字段,那么才能發(fā)揮出B+樹(shù)快速二分查 找的優(yōu)勢(shì)來(lái)。

7.2.3 where與order by沖突時(shí)優(yōu)先where

因?yàn)榇蠖鄶?shù)情況基于索引進(jìn)行where篩選往往可以最快速度篩選出你要的少部分?jǐn)?shù)據(jù),然后做排序的成本可能會(huì)小很多。

7.2.4 分頁(yè)查詢(xún)優(yōu)化
select * from A ORDER BY name limit 90000,5;

可以讓排序和分頁(yè)操作先查出主鍵,然后根據(jù)主鍵查到對(duì)應(yīng)的記錄,SQL改寫(xiě)如下

select * from employees e inner join (select id from employees order by name limit 90000,5) ed on e.id= ed.id;
7.2.5 對(duì)于join關(guān)聯(lián)sql的優(yōu)化

1.關(guān)聯(lián)字段加索引
2.小表驅(qū)動(dòng)大表,寫(xiě)多表連接sql時(shí)如果明確知道哪張表是小表可以用straight_join寫(xiě)法固定連接驅(qū)動(dòng)方式,省去mysql優(yōu)化器自己判斷的時(shí)間
straight_join解釋?zhuān)簊traight_join功能同join類(lèi)似,但能讓左邊的表來(lái)驅(qū)動(dòng)右邊的表,能改表優(yōu)化器對(duì)于聯(lián)表查詢(xún)的執(zhí)行順序。
比如:select * from t2 straight_join t1 on t2.a = t1.a; 代表指定mysql選著 t2 表作為驅(qū)動(dòng)表。
straight_join只適用于inner join,并不適用于left join,right join。(因?yàn)閘eft join,right join已經(jīng)代表指定了表的執(zhí)行順序)
盡可能讓優(yōu)化器去判斷,因?yàn)榇蟛糠智闆r下mysql優(yōu)化器是比人要聰明的。使用straight_join一定要慎重,因?yàn)椴糠智闆r下人為指定的執(zhí)行順序并不一定會(huì)比優(yōu)化引擎要靠譜。
3.對(duì)于小表定義的明確
在決定哪個(gè)表做驅(qū)動(dòng)表的時(shí)候,應(yīng)該是兩個(gè)表按照各自的條件過(guò)濾,過(guò)濾完成之后,計(jì)算參與 join 的各個(gè)字段的總數(shù)據(jù)量,數(shù)據(jù)量小的那個(gè)表,就是“小表”,應(yīng)該作為驅(qū)動(dòng)表。
4.in和exsits優(yōu)化
原則:小表驅(qū)動(dòng)大表,即小的數(shù)據(jù)集驅(qū)動(dòng)大的數(shù)據(jù)集
in:當(dāng)B表的數(shù)據(jù)集小于A表的數(shù)據(jù)集時(shí),in優(yōu)于exists

select * from A where id in (select id from B)

等價(jià)于:
for(select id from B){
  select * from A where A.id= B.id
}

exists:當(dāng)A表的數(shù)據(jù)集小于B表的數(shù)據(jù)集時(shí),exists優(yōu)于in

select * from A where exists (select 1 from B where B.id = A.id)

等價(jià)于:
for(select * from A){
select * from B where B.id = A.id
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容