必看,关于sql的慢查询及解决方案
文章目錄一、前言二、慢查詢概要2.1 第一步,慢查詢分析之前的配置2.1.1 方式一:修改my.ini2.1.2 方式二:修改數(shù)據(jù)庫2.2 第二步,找到執(zhí)行慢的sql語句2.3 第三步,找到原因兩種方式之一,explain分析,explain各個(gè)字段的解釋2.4 第四步,找到原因兩種方式之一,profile分析,找到慢查詢的本質(zhì)原因,profile各個(gè)字段的解釋2.4.1 explain制造慢sq
文章目錄
- 一、前言
- 二、慢查詢概要
-
- 2.1 第一步,慢查詢分析之前的配置
-
- 2.1.1 方式一:修改my.ini
- 2.1.2 方式二:修改數(shù)據(jù)庫
- 2.2 第二步,找到執(zhí)行慢的sql語句
- 2.3 第三步,找到原因兩種方式之一,explain分析,explain各個(gè)字段的解釋
- 2.4 第四步,找到原因兩種方式之一,profile分析,找到慢查詢的本質(zhì)原因,profile各個(gè)字段的解釋
-
- 2.4.1 explain制造慢sql語句,profile找到慢的sql語句
- 2.4.2 show profile for query 具體id
-
- 2.4.2.1 sending data發(fā)送數(shù)據(jù)慢,就是網(wǎng)絡(luò)IO
- 2.4.2.2 Copying to tmp table臨時(shí)表慢
- 2.4.3 查看cpu、內(nèi)存和IO信息
- 三、SQL優(yōu)化
-
- 3.1 第一,明確SQL優(yōu)化兩個(gè)目標(biāo)(IO成本+CPU成本)
- 3.2 第二,SQL執(zhí)行的11個(gè)步驟,搞懂MySQL的語句執(zhí)行順序(11條)
- 3.3 第三,表設(shè)計(jì)(表設(shè)計(jì)5條 + 字段設(shè)計(jì)5條)
-
- 3.3.1 表設(shè)計(jì)層面(5條)
- 3.3.2 字段設(shè)計(jì)層面(5條)
- 3.4 第四,高效的SQL語句(索引+單表優(yōu)化+多表優(yōu)化)
-
- 3.4.1 索引(索引兩方向:優(yōu)先使用索引,避免索引失效)
-
- 3.4.1.1 避免索引失效
- 3.4.1.2 優(yōu)先使用索引
- 3.4.2 單表查詢 10點(diǎn)
-
- 1、單表查詢:避免索引失效,不要在 where 子句中對(duì)字段進(jìn)行 null 值判斷,這會(huì)進(jìn)行全表掃描
- 2、單表查詢,開啟慢查詢?nèi)罩?#xff0c;定位慢查詢的SQL語句
- 3、單表查詢,數(shù)據(jù)庫壓力轉(zhuǎn)移到Java后臺(tái)
- 4、單表查詢,where 查詢條件數(shù)量 + 查詢條件順序
- 5、單表查詢,返回值和關(guān)鍵字大寫優(yōu)化(select *,limit)
- 6、單表查詢:計(jì)數(shù)優(yōu)化,不要使用 count(列名)或 count(常量)來替代 count(*)
- 7、單表查詢:去重優(yōu)化,能用GROUP BY的就不用DISTINCT
- 8、單表查詢,LIMIT優(yōu)化,巧借索引優(yōu)化orderby排序
- 9、單表查詢:排序優(yōu)化,order by 字段加索引
- 10、單表查詢:分組優(yōu)化,group by 調(diào)節(jié)順序?yàn)閺?fù)合索引順序
- 3.4.3 多表連接查詢 4點(diǎn)
-
- 1、多表連接,五個(gè):用 join 代替子查詢,連接字段類型一致,連接字段加索引,主表是小表,orderby和groupby字段在主表,否則無用
- 2、多表查詢,很多時(shí)候用exists代替in是一個(gè)好的選擇(理由:雖然in與exists效率在不同場景效率有高有低,但not exists比not in的效率高)
- 3、盡量避免向客戶端返回大數(shù)據(jù)量,應(yīng)該使用分頁
- 4、用Union ALL代替OR(比較少用,一般都用OR)
- 3.5 第五,通用
- 四、小結(jié)
一、前言
SQL中,廣義的查詢就是crud操作,狹義的查詢僅僅是select查詢操作,慢查詢就是指廣義的查詢,否則為什么不叫慢查詢、慢更新、慢刪除。
慢查詢就是那些執(zhí)行慢的sql語句,包括crud,一般是查詢,所以稱為慢查詢
問題1:怎么一定一個(gè)sql語句是慢的?
回答:根據(jù)實(shí)際需要,如果前端反饋,執(zhí)行3s是慢的,就是在my.ini中(Windows是my.ini,Linux是my.conf)中設(shè)置long_query_time=3,表示操作3s的查詢就是慢查詢,要記錄下來,好好分析。
問題:為什么這些sql語句會(huì)慢呢?為什么這些sql語句滿足long_query_time設(shè)置的時(shí)間?
回答:執(zhí)行慢的sql語句不滿足我們的要求,
(1)找到原因 explain + profile;
(2)要想辦法解決,這就是SQL優(yōu)化。
二、慢查詢概要
2.1 第一步,慢查詢分析之前的配置
2.1.1 方式一:修改my.ini
修改配置文件 在 my.ini 增加幾行:
[mysqld] long_query_time=2 // 慢查詢的定義時(shí)間(超過2秒就是慢查詢)// 慢查詢log日志記錄( slow_query_log) ## 5.5 版本以下配置 log-slow-queries="mysql-slow-query.log" ## 5.5 版本及以上配置 slow-query-log=on slow_query_log_file="mysql-slow-query.log"//記錄沒有使用索引的query log-query-not-using-indexes2.1.2 方式二:修改數(shù)據(jù)庫
mysql > set global slow_query_log=ON mysql > set global long_query_time = 3600; mysql > set global long_queries_not_using_indexes=ON;2.2 第二步,找到執(zhí)行慢的sql語句
2.3 第三步,找到原因兩種方式之一,explain分析,explain各個(gè)字段的解釋
問題:為什么使用explain來做慢查詢分析?
回答:explain關(guān)鍵字可以模擬優(yōu)化器執(zhí)行SQL查詢語句,所以用來分析sql慢查詢語句
使用explain分析這10條sql:
explain SQL_NO_CACHE select * from emp where name = 'Jefabc'注意:mysql8.0以下,SQL_NO_CACHE 指明了該查詢不走緩存,避免了查詢速度時(shí)高時(shí)低,影響判斷。
ps:使用explain一般看一看索引使用是否正確,盡量避免回表。
explain各個(gè)字段解釋
id:每個(gè)執(zhí)行計(jì)劃都有一個(gè) id,如果是一個(gè)聯(lián)合查詢union,這里還將有多個(gè) id。
select_type:表示 SELECT 查詢類型,常見的有四種
SIMPLE(普通查詢,即沒有聯(lián)合查詢、子查詢)、
PRIMARY(主查詢)、UNION(UNION 中后面的查詢)、SUBQUERY(子查詢)等。
table:當(dāng)前執(zhí)行計(jì)劃查詢的表,如果給表起別名了,則顯示別名信息。
partitions:訪問的分區(qū)表信息。
type:這是重要的列,顯示連接使用了何種類型。從最好到最差的連接類型為:system > const > eq_ref > ref > range > index > ALL。
system/const:表中只有一行數(shù)據(jù)匹配,此時(shí)根據(jù)索引查詢一次就能找到對(duì)應(yīng)的數(shù)據(jù)。如果是 B + 樹索引,我們知道此時(shí)索引構(gòu)造成了多個(gè)層級(jí)的樹,當(dāng)查詢的索引在樹的底層時(shí),查詢效率就越低。const 表示此時(shí)索引在第一層,只需訪問一層便能得到數(shù)據(jù)。
eq_ref:使用唯一索引掃描,常見于多表連接中使用主鍵和唯一索引作為關(guān)聯(lián)條件。
ref:非唯一索引掃描,還可見于唯一索引最左原則匹配掃描。
range:索引范圍掃描,比如,<,>,between 等操作。
index:索引全表掃描,此時(shí)遍歷整個(gè)索引樹。
ALL:表示全表掃描,需要遍歷全表來找到對(duì)應(yīng)的行。
possible_keys:可能使用到的索引。
key:實(shí)際使用到的索引。
key_len:實(shí)際使用的索引的長度。
ref:關(guān)聯(lián) id 等信息。
rows:查找到記錄所掃描的行數(shù)。
filtered:查找到所需記錄占總掃描記錄數(shù)的比例。
Extra:額外的信息。
2.4 第四步,找到原因兩種方式之一,profile分析,找到慢查詢的本質(zhì)原因,profile各個(gè)字段的解釋
問題:為什么使用profile做慢查詢分析?
回答:Show profile 是mysql 提供可以用來分析當(dāng)前會(huì)話中語句執(zhí)行的資源消耗情況。
以下四個(gè)中若出現(xiàn)一個(gè)或多個(gè),表示sql 語句 必須優(yōu)化。
1、converting HEAP to MyISAM : 查詢結(jié)果太大,內(nèi)存都不夠用了,往磁盤上搬了;
2、creating tmp table :創(chuàng)建臨時(shí)表,拷貝數(shù)據(jù)到臨時(shí)表,然后再刪除;
3、copying to tmp table on disk :把內(nèi)存中臨時(shí)表復(fù)制到磁盤,危險(xiǎn)!!!
4、 locked
2.4.1 explain制造慢sql語句,profile找到慢的sql語句
第一步,查看一下我的數(shù)據(jù)庫版本
Show profiles是5.0.37之后添加的,要想使用此功能,要確保版本在5.0.37之后。
mysql> Select version();| version() | | 5.0.82-community-nt |版本是支持show profiles功能的。接下來進(jìn)入mysql性能跟蹤診斷的世界
第二步,查看是否打開了profiles功能,默認(rèn)是關(guān)閉的
mysql> use test;Database changedmysql> show profiles;Empty set (0.00 sec)顯示為空,說明profiles功能是關(guān)閉的。
第三步,開啟profile
mysql> set profiling=1;Query OK, 0 rows affected (0.00 sec)第四步,explain執(zhí)行下面的查詢
mysql> explain select distinct player_idfrom task limit 20;mysql> select distinct player_id from task ;第五步,執(zhí)行 show profiles
mysql> show profiles; | Query_ID | Duration | Query || 1 | 0.00035225 | explain select distinct player_id from task limit 20 | | 2 | 1.91772775 | select distinct player_id from task |Query_ID 這個(gè)需要就是給show profile for query 具體id來用的。
此時(shí)可以看到執(zhí)行select distinct player_id from task 用了1.91772775秒的時(shí)間
第六步,調(diào)整hostory大小
注意如果hostory比較小可以適當(dāng)調(diào)大,不然query不到
SET profiling_history_size=100?
2.4.2 show profile for query 具體id
2.4.2.1 sending data發(fā)送數(shù)據(jù)慢,就是網(wǎng)絡(luò)IO
根據(jù)prifile進(jìn)一步分析,
show profile for query 具體id可以清楚的看到該sql的所有執(zhí)行階段,如鎖等待、執(zhí)行、優(yōu)化、發(fā)送數(shù)據(jù)、內(nèi)存排序,在下圖中可以看到Sending data發(fā)送數(shù)據(jù)耗時(shí)1.39s。慢查詢主要原因是網(wǎng)絡(luò)IO。
2.4.2.2 Copying to tmp table臨時(shí)表慢
**根據(jù)query_id 查看某個(gè)查詢的詳細(xì)時(shí)間耗費(fèi),是Copying to tmp table **
mysql> show profile for query 2;| Status | Duration || starting | 0.000052 | | Opening tables | 0.000009 | | System lock | 0.000003 | | Table lock | 0.000007 | | init | 0.000013 | | optimizing | 0.000003 | | statistics | 0.000009 | | preparing | 0.000008 | | Creating tmp table | 0.000074 | | executing | 0.000002 | | Copying to tmp table |1.916551 | // 大量時(shí)間 四條中第三條,很嚴(yán)重,臨時(shí)表到 | Sending data | 0.000667 | | end | 0.000004 | | removing tmp table | 0.000065 | | end | 0.000002 | | end | 0.000002 | | query end | 0.000003 | | freeing items | 0.000245 | | closing tables | 0.000006 | | logging slow query | 0.000002 | | cleaning up | 0.000003 |可以看到 Copying to tmp table 部分耗費(fèi)了大量時(shí)間,這是因?yàn)閐istinct查看會(huì)用到臨時(shí)表
2.4.3 查看cpu、內(nèi)存和IO信息
那么可不可以查看占用cpu、 io等信息呢
mysql> show profile block io,cpu for query2; | Status | Duration | CPU_user |CPU_system | Block_ops_in | Block_ops_out || starting | 0.000052 | NULL | NULL | NULL | NULL | | Opening tables | 0.000009 | NULL | NULL | NULL | NULL | | System lock | 0.000003 | NULL | NULL | NULL | NULL | | Table lock | 0.000007 | NULL | NULL | NULL | NULL | | init | 0.000013 | NULL | NULL | NULL | NULL | | optimizing | 0.000003 | NULL | NULL | NULL | NULL | | statistics | 0.000009 | NULL | NULL | NULL | NULL | | preparing | 0.000008 | NULL | NULL | NULL | NULL | | Creating tmp table | 0.000074 | NULL | NULL | NULL | NULL | | executing | 0.000002 | NULL | NULL | NULL | NULL | | Copying to tmp table(耗時(shí)) | 1.916551 | NULL | NULL | NULL |NULL | | Sending data | 0.000667 | NULL | NULL | NULL | NULL | | end | 0.000004 | NULL | NULL | NULL | NULL | | removing tmp table | 0.000065 | NULL | NULL | NULL | NULL | | end | 0.000002 | NULL | NULL | NULL | NULL | | end | 0.000002 | NULL | NULL | NULL | NULL | | query end | 0.000003 | NULL | NULL | NULL | NULL | | freeing items | 0.000245 | NULL | NULL | NULL | NULL | | closing tables | 0.000006 | NULL | NULL | NULL | NULL | | logging slow query | 0.000002 | NULL | NULL | NULL | NULL | | cleaning up | 0.000003 | NULL | NULL | NULL | NULL |另外還可以看到memory,swaps,context switches,source 等信息
三、SQL優(yōu)化
3.1 第一,明確SQL優(yōu)化兩個(gè)目標(biāo)(IO成本+CPU成本)
減少 IO 次數(shù)
IO永遠(yuǎn)是數(shù)據(jù)庫最容易產(chǎn)生瓶頸的地方,這是由數(shù)據(jù)庫的職責(zé)所決定的,大部分?jǐn)?shù)據(jù)庫操作中超過90%的時(shí)間都是IO操作所占用的,減少IO次數(shù)是SQL優(yōu)化中需要第一優(yōu)先考慮,當(dāng)然,也是收效最明顯的優(yōu)化手段。
降低 CPU 計(jì)算
除了 IO 瓶頸之外,SQL優(yōu)化中需要考慮的就是 CPU 運(yùn)算量的優(yōu)化了。order by,group by,distinct等操作都十分占用CPU資源(這些操作基本上都是 CPU 處理內(nèi)存中的數(shù)據(jù)比較運(yùn)算)。當(dāng)我們的 IO 優(yōu)化做到一定階段之后,降低 CPU 計(jì)算也就成為了我們 SQL 優(yōu)化的重要目標(biāo)。
3.2 第二,SQL執(zhí)行的11個(gè)步驟,搞懂MySQL的語句執(zhí)行順序(11條)
MySQL的語句一共分為11步,如下圖所標(biāo)注的那樣,最先執(zhí)行的總是FROM操作,最后執(zhí)行的是LIMIT操作。其中每一個(gè)操作都會(huì)產(chǎn)生一張?zhí)摂M的表,這個(gè)虛擬的表作為一個(gè)處理的輸入。
select m.Province,S.Name from member m
left join ShippingArea s on m.Province=s.ShippingAreaID;
下面具體分析一下查詢處理的每一個(gè)階段。
1、FORM: 對(duì)FROM的左邊的表和右邊的表計(jì)算笛卡爾積。產(chǎn)生虛表vitual table 1
2、ON: 對(duì)虛表VT1進(jìn)行ON篩選,只有那些符合< join-condition>的行才會(huì)被記錄在虛表VT2中。
3、JOIN: 如果指定了OUTER JOIN(比如left join、 right join),那么保留表中未匹配的行就會(huì)作為外部行添加到虛擬表VT2中,產(chǎn)生虛擬表VT3,如果包含兩個(gè)以上的表的話,那么就會(huì)對(duì)上一個(gè)join連接產(chǎn)生的結(jié)果VT3和下一個(gè)表重復(fù)執(zhí)行步驟1~3這三個(gè)步驟,一直到處理完所有的表為止。
4、WHERE: 對(duì)虛擬表VT3進(jìn)行WHERE條件過濾。只有符合< where-condition>的記錄才會(huì)被插入到虛擬表VT4中。
5、GROUP BY: 根據(jù)group by子句中的列,對(duì)VT4中的記錄進(jìn)行分組操作,產(chǎn)生VT5。
6、CUBE | ROLLUP: 對(duì)表VT5進(jìn)行cube或者rollup操作,產(chǎn)生表VT6。
7、HAVING: 對(duì)虛擬表VT6應(yīng)用having過濾,只有符合< having-condition>的記錄才會(huì)被插入到虛擬表VT7中。
8、SELECT: 執(zhí)行select操作,選擇指定的列,插入到虛擬表VT8中。
9、DISTINCT: 對(duì)VT8中的記錄進(jìn)行去重。產(chǎn)生虛擬表VT9。
10、ORDER BY: 將虛擬表VT9中的記錄進(jìn)行排序操作,產(chǎn)生虛擬表VT10。
11、 LIMIT:取出指定行的記錄,產(chǎn)生虛擬表VT11,并將結(jié)果返回。
金手指:記憶方式:輸入、判斷、返回
輸入:from on join
判斷:where判斷、group by分組、having判斷
返回:select輸出,distinct去重、order by排序、limit返回
單表查詢,設(shè)置條件:用 Where 子句替換 HAVING 子句 因?yàn)?HAVING 只會(huì)在檢索出所有記錄之后才對(duì)結(jié)果集進(jìn)行過濾
大多數(shù)關(guān)系型數(shù)據(jù)庫都是按照行(row)的方式存儲(chǔ),而數(shù)據(jù)存取操作都是以一個(gè)固定大小的IO單元(被稱作block或者page)為單位,一般為4KB,8KB…… 大多數(shù)時(shí)候,每個(gè)IO單元中存儲(chǔ)了多行,每行都是存儲(chǔ)了該行的所有字段(blob等特殊類型字段除外)。
所以,我們是取一個(gè)字段還是多個(gè)字段,實(shí)際上數(shù)據(jù)庫在表中需要訪問的數(shù)據(jù)量其實(shí)是一樣的。
當(dāng)然,也有例外情況,那就是我們的這個(gè)查詢?cè)谒饕芯涂梢酝瓿?#xff0c;也就是說當(dāng)只取 a,b兩個(gè)字段的時(shí)候,不需要回表,而c這個(gè)字段不在使用的索引中,需要回表取得其數(shù)據(jù)。在這樣的情況下,二者的IO量會(huì)有較大差異。
大多數(shù)時(shí)候并不會(huì)影響到 IO 量,但是當(dāng)還存在order by操作的時(shí)候,select子句中的字段多少會(huì)在很大程度上影響到排序效率。
3.3 第三,表設(shè)計(jì)(表設(shè)計(jì)5條 + 字段設(shè)計(jì)5條)
3.3.1 表設(shè)計(jì)層面(5條)
1、【表引擎】選擇正確的表引擎 MyISAM Innodb
(1)MyISAM 適合于一些需要大量查詢的應(yīng)用,但其對(duì)于有大量寫操作并不是很好,甚至你只是需要update一個(gè)字段,整個(gè)表都會(huì)被鎖起來,而別的進(jìn)程,就算是讀進(jìn)程都無法操作直到讀操作完成。(2)MyISAM 對(duì)于 SELECT COUNT(*) 這類的計(jì)算是超快無比的。
InnoDB 的趨勢(shì)會(huì)是一個(gè)非常復(fù)雜的存儲(chǔ)引擎,對(duì)于一些小的應(yīng)用,它會(huì)比 MyISAM 還慢。他是它支持“行鎖” ,于是在寫操作比較多的時(shí)候,會(huì)更優(yōu)秀。并且,他還支持更多的高級(jí)應(yīng)用,比如:事務(wù)。
金手指:與傳統(tǒng)的ISAM與MyISAM相比,InnoDB的最大特色就是支持了ACID兼容的事務(wù)(Transaction)功能。
2、【表設(shè)計(jì)-無邏輯意義自增id】給所有的InnoDB表都設(shè)計(jì)一個(gè)無邏輯意義的自增列做主鍵,對(duì)于絕大多數(shù)場景都是如此,真正純只讀用InnoDB表的并不多;
3、【表設(shè)計(jì)-反范式設(shè)計(jì),減少回表】適當(dāng)?shù)氖褂米侄稳哂嗟姆捶妒皆O(shè)計(jì),以空間換時(shí)間有的時(shí)候會(huì)很高效,比如:有用戶id字段的表,用戶在查詢時(shí)如果經(jīng)常需要同時(shí)獲得用戶名,此時(shí)可以將用戶名當(dāng)一個(gè)冗余字段在該表中存儲(chǔ),這樣就可以不做連接即可獲得用戶名。
要求:該冗余字段的值一般不變或者很少變化。
4、【表存儲(chǔ)——數(shù)據(jù)量過大,水平分表】通常地,單表物理大小不超過10GB,單表行數(shù)不超過1億條,行平均長度不超過8KB,如果機(jī)器性能足夠,這些數(shù)據(jù)量MySQL是完全能處理的過來的,不用擔(dān)心性能問題,數(shù)據(jù)量過大就要水平分表,不要放在同一個(gè)表中。
5、【表存儲(chǔ)——表中存儲(chǔ)數(shù)據(jù)而不是運(yùn)算】數(shù)據(jù)庫直接存放數(shù)值,不要在數(shù)據(jù)庫中做運(yùn)算。
優(yōu)化數(shù)據(jù)庫結(jié)構(gòu)
合理的數(shù)據(jù)庫結(jié)構(gòu)不僅可以使數(shù)據(jù)庫占用更小的磁盤空間,而且能夠使查詢速度更快。數(shù)據(jù)庫結(jié)構(gòu)的設(shè)計(jì),需要考慮數(shù)據(jù)冗余、查詢和更新的速度、字段的數(shù)據(jù)類型是否合理等多方面的內(nèi)容。
1、垂直分表,將字段很多的表分解成多個(gè)表
對(duì)于字段比較多的表,如果有些字段的使用頻率很低,可以將這些字段分離出來形成新表。因?yàn)楫?dāng)一個(gè)表的數(shù)據(jù)量很大時(shí),會(huì)由于使用頻率低的字段的存在而變慢。
2、多對(duì)多關(guān)系表,增加中間表
對(duì)于需要經(jīng)常聯(lián)合查詢的表,可以建立中間表以提高查詢效率。通過建立中間表,把需要經(jīng)常聯(lián)合查詢的數(shù)據(jù)插入到中間表中,然后將原來的聯(lián)合查詢改為對(duì)中間表的查詢,以此來提高查詢效率。
三范式優(yōu)化+反范式優(yōu)化+分庫分表
1)三大范式優(yōu)化: 比如消除冗余(節(jié)省空間。。)
2)反范式優(yōu)化:比如適當(dāng)加冗余等(減少 join)
3)分庫: 分區(qū)將數(shù)據(jù)在物理上分隔開,不同分區(qū)的數(shù)據(jù)可以制定保存在處于不同磁盤上的數(shù)據(jù)文件里。這樣,當(dāng)對(duì)這個(gè)表進(jìn)行查詢時(shí),只需要在表分區(qū)中進(jìn)行掃描,而不必進(jìn)行全表掃描,明顯縮短了查詢時(shí)間,另外處于不同磁盤的分區(qū)也將對(duì)這個(gè)表的數(shù)據(jù)傳輸分散在不同的磁盤 I/O,一個(gè)精心設(shè)置的分區(qū)可以將數(shù)據(jù)傳輸對(duì)磁盤 I/O 競爭均勻地分散開。對(duì)數(shù)據(jù)量大的時(shí)時(shí)表可采取此方法。可按月自動(dòng)建表分區(qū)。
4)分表:垂直拆分和水平拆分:
案例: 簡單購物系統(tǒng)暫設(shè)涉及如下表:
1.產(chǎn)品表(數(shù)據(jù)量 10w,穩(wěn)定)
2.訂單表(數(shù)據(jù)量 200w,且有增長趨勢(shì))
3.用戶表 (數(shù)據(jù)量 100w,且有增長趨勢(shì))
以 mysql 為例講述下水平拆分和垂直拆分,mysql 能容忍的數(shù)量級(jí)在百萬靜態(tài)數(shù)據(jù)可以到千萬
垂直拆分:
解決問題:表與表之間的 io 競爭
不解決問題:單表中數(shù)據(jù)量增長出現(xiàn)的壓力
方案: 把產(chǎn)品表和用戶表放到一個(gè) server 上 訂單表單獨(dú)放到一個(gè) server 上
水平拆分:
解決問題:單表中數(shù)據(jù)量增長出現(xiàn)的壓力
不解決問題:表與表之間的 io 爭奪
方案:
用戶表通過性別拆分為男用戶表和女用戶表,男用戶表放一個(gè) server 上 女用戶表放一個(gè) server 上(女的愛購物)
訂單表通過已完成和完成中拆分為已完成訂單和未完成訂單 ,未完成訂單放一個(gè) server 上 已完成訂單放到一個(gè)server上
3.3.2 字段設(shè)計(jì)層面(5條)
1、yes 兩條【字段-數(shù)據(jù)類型夠用選小的,加上not null提交性能】字段長度滿足需求前提下,盡可能選擇長度小的;字段屬性盡量都加上NOT NULL約束,可一定程度提高性能;
2、no 過大不要【字段-數(shù)據(jù)類型盡量不使用Text/Blob,垂直分表】盡可能不使用TEXT/BLOB類型,確實(shí)需要的話,建議垂直分表,拆分到子表中,不要和主表放在一起,避免SELECT * 的時(shí)候讀性能太差。
3、select 1條【字段-使用數(shù)字型而不是字符型】數(shù)據(jù)類型盡量用數(shù)字型,數(shù)字型比字符型的效率高,如果表中某個(gè)字段存儲(chǔ)的都是數(shù)字,那么該字段設(shè)計(jì)為數(shù)字型字段,而不要設(shè)計(jì)為字符型,這會(huì)降低查詢和連接的性能,并會(huì)增加存儲(chǔ)開銷。這是因?yàn)橐嬖谔幚聿樵兒瓦B接時(shí)會(huì)逐個(gè)比較字符串中每一個(gè)字符,而對(duì)于數(shù)字型而言只需要比較一次就夠了。
4、select 1條【字段-接上面,對(duì)于字符類型,使用固定長度而不是可變長度】
char/nchar為固定長度,如果某個(gè)字段中字符長度已知固定,使用char/nchar效率比varchar/nvarchar效率高。
5、select 1條【取值有限用枚舉Enum】
如果你有一個(gè)字段,比如“性別”,“國家”,“民族”,“狀態(tài)”或“部門”,你知道這些字段的取值是有限而且固定的,那么,你應(yīng)該使用 ENUM 而不是 VARCHAR。
3.4 第四,高效的SQL語句(索引+單表優(yōu)化+多表優(yōu)化)
3.4.1 索引(索引兩方向:優(yōu)先使用索引,避免索引失效)
如下第二、第三句SQL,使用索引與不使用索引,查詢速度相差巨大。(索引可用在select后,也可用于on,where后的條件中,參考索引使用理論基礎(chǔ))
SELECT * FROM test1; -- 1.863 2.008 2.062(260萬條數(shù)據(jù)) -- 此處有個(gè)select*的誤區(qū),實(shí)際上不加索引的select* 比select col速度來得快SELECT NAME FROM test1; -- 4.989 5.001 4.855 (260萬條數(shù)據(jù), name不加索引)SELECT NAME FROM test1; -- 1.227 1.476 1.403 (260萬條數(shù)據(jù), name加索引)3.4.1.1 避免索引失效
1)應(yīng)盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進(jìn)行全表掃描。
2)應(yīng)盡量避免在 where 子句中對(duì)字段進(jìn)行 null 值判斷,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。
如:
select id from t where num is null
可以在 num 上設(shè)置默認(rèn)值 0,確保表中 num列沒有 null 值,然后這樣查詢:
select id from t where num=0
注意:關(guān)于 null,isNull,isNotNull 其實(shí)是要看成本的,是否回表等因素總和考慮,才會(huì)決定是要走索引還是走全表掃描
【索引】禁止任何對(duì)where字段的計(jì)算,函數(shù)等操作
索引沒起作用的情況
1、使用LIKE關(guān)鍵字的查詢語句
在使用LIKE關(guān)鍵字進(jìn)行查詢的查詢語句中,如果匹配字符串的第一個(gè)字符為“%”,索引不會(huì)起作用。只有“%”不在第一個(gè)位置索引才會(huì)起作用。
2、使用多列索引的查詢語句
MySQL可以為多個(gè)字段創(chuàng)建索引。一個(gè)索引最多可以包括16個(gè)字段。對(duì)于多列索引,只有查詢條件使用了這些字段中的第一個(gè)字段時(shí),索引才會(huì)被使用。
盡量避免在索引過的字符數(shù)據(jù)中,使用非打頭字母搜索,導(dǎo)致索引無效
SELECT id FROM table WHERE NAME LIKE ‘%L%’; // 錯(cuò)誤SELECT id FROM table WHERE NAME LIKE ‘L%’; // 正確即使NAME字段建有索引, 第一個(gè)查詢無法利用索引完成加快操作,引擎不得不對(duì)全表所有數(shù)據(jù)逐條操作來完成任務(wù)。而第二個(gè)查詢能夠使用索引來加快操作。
不要在where子句中的“=”左邊進(jìn)行函數(shù)、算術(shù)運(yùn)算或其他表達(dá)式運(yùn)算,導(dǎo)致索引無效
任何對(duì)列的操作都將導(dǎo)致表掃描,它包括數(shù)據(jù)庫函數(shù)、計(jì)算表達(dá)式等等,查詢時(shí)要盡可能將操作移至等號(hào)右邊。
SELECT * FROM T1 WHERE F1/2=100應(yīng)改為:SELECT * FROM T1 WHERE F1=100*2 SELECT * FROM RECORD WHERE SUBSTRING(CARD_NO,1,4)= '5378'應(yīng)改為:SELECT * FROM RECORD WHERE CARD_NO LIKE '5378%' SELECT member_number, first_name, last_name FROM membersWHERE DATEDIFF(yy,datofbirth,now()) > 21應(yīng)改為:SELECT member_number, first_name, last_name FROM membersWHERE dateofbirth < DATEADD(yy,-21,now())索引:
%% like查詢, not in,not exist 都無法應(yīng)用索引
用了"聚合函數(shù)"查詢,就無法應(yīng)用索引;
復(fù)合索引,一般都是看第一個(gè)條件索引
3.4.1.2 優(yōu)先使用索引
【索引-前綴索引】對(duì)一個(gè)VARCHAR(N)列創(chuàng)建索引時(shí),通常取其50%(甚至更小)左右長度創(chuàng)建前綴索引就足以滿足80%以上的查詢需求了,沒必要?jiǎng)?chuàng)建整列的全長度索引;
【SQL語句-輸入?yún)?shù)where子句-索引代替全表掃描】盡量避免在where子句中對(duì)字段進(jìn)行null值判斷。這會(huì)進(jìn)行全表掃描 Select id,name from user where name is null;
【SQL語句-輸入?yún)?shù)where子句-索引代替全表掃描】盡量避免在where子句中對(duì)字段進(jìn)行表達(dá)式操作。這會(huì)導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描 SELECT id,name FROM user where age/12;
【優(yōu)先使用復(fù)合索引代替單個(gè)索引,減少索引數(shù)量】多用復(fù)合索引,少用多個(gè)獨(dú)立索引,尤其是一些基數(shù)(Cardinality)太小(比如說,該列的唯一值總數(shù)少于255)的列就不要?jiǎng)?chuàng)建獨(dú)立索引了;
【SQL語句-輸入?yún)?shù)or子句-索引代替全表掃描】對(duì)于OR子句,如果要利用索引,則OR之間的每個(gè)條件列都必須用到索引,如果沒有索引,則應(yīng)該考慮增加索引。
【復(fù)合索引的順序】在使用索引字段作為條件時(shí),如果該索引是復(fù)合索引,那么必須使用到該索引中的第一個(gè)字段作為條件時(shí)才能保證系統(tǒng)使用該索引,否則該索引將不會(huì)被使用,并且應(yīng)盡可能的讓字段順序與索引順序相一致。
3.4.2 單表查詢 10點(diǎn)
1、單表查詢:避免索引失效,不要在 where 子句中對(duì)字段進(jìn)行 null 值判斷,這會(huì)進(jìn)行全表掃描
錯(cuò)誤:應(yīng)盡量避免在 where 子句中對(duì)字段進(jìn)行 null 值判斷,否則將導(dǎo)致數(shù)據(jù)庫放棄使用索引而進(jìn)行全表掃描,降低查詢速度。可以在col上設(shè)置默認(rèn)值,確保表中col列not null值,然后查詢。
以下實(shí)測結(jié)果表明:在不加索引的情況下,判空操作比判非空默認(rèn)值操作速度快得多,加索引的情況下兩種情況差不多。
SELECT id from table where col is null –- 2.003 1.963 1.975 (table col 未加入索引,260萬數(shù)據(jù)) SELECT id from table where col = ‘0’ -– 4.407 5.256 5.434 (table col 未加入索引,260萬數(shù)據(jù))SELECT id from table where col is null –- 1.549 1.884 1.818 (table col 加入索引,260萬數(shù)據(jù)) SELECT id from table where col = ‘0’ -– 1.626 1.854 1.532 (table col 加入索引,260萬數(shù)據(jù))2、單表查詢,開啟慢查詢?nèi)罩?#xff0c;定位慢查詢的SQL語句
【單表查詢-慢查詢】開啟慢查詢?nèi)罩?#xff0c;定期用explain或desc優(yōu)化慢查詢中的SQL語句
【單表查詢-預(yù)處理語句】使用預(yù)處理語句。例如使用PDO來操作mysql
在性能方面,當(dāng)一個(gè)相同的查詢被使用多次的時(shí)候,這會(huì)為你帶來可觀的性能優(yōu)勢(shì)。你可以給這些Prepared Statements(預(yù)處理語句)定義一些參數(shù),而MySQL只會(huì)解析一次。
3、單表查詢,數(shù)據(jù)庫壓力轉(zhuǎn)移到Java后臺(tái)
將數(shù)據(jù)庫的壓力轉(zhuǎn)移至后臺(tái)
數(shù)據(jù)庫的資源是寶貴的,我們可以將不必要的操作轉(zhuǎn)移至Java端處理。如判空,字段值拼接,字符串TRIM,日期格式化等等。
如下SQL,均可由Java處理。
4、單表查詢,where 查詢條件數(shù)量 + 查詢條件順序
where子句兩個(gè)
第一,查詢條件盡量少
第二,越精確的條件,應(yīng)該放在前面,先過濾掉大數(shù)據(jù);
where子句中,盡量少的條件
如下面這句SQL,如果?where條件中col1=1,col2=2是能夠精確查找結(jié)果的最簡條件,則無需加入冗余的其他條件。
SELECT * FROM table where col1=1 and col2=2 and col3=3
where子句中,越精準(zhǔn)的條件越早過濾
where條件中,越精確的條件(能夠剔除大部分結(jié)果的條件)放前面。
又如,在多個(gè)表進(jìn)行分頁數(shù)據(jù)查詢的時(shí)候,我們最好是能夠在一個(gè)表上先過濾好數(shù)據(jù)分好頁,然后再用分好頁的結(jié)果集與另外的表Join,這樣可以盡可能多的減少不必要的 IO 操作,大大節(jié)省IO操作所消耗的時(shí)間。
5、單表查詢,返回值和關(guān)鍵字大寫優(yōu)化(select *,limit)
【SQL語句-返回值】滿足業(yè)務(wù)需求下,返回值不要使用select * from 的形式查詢數(shù)據(jù),只查詢需要使用的列
【SQL語句-返回值】滿足業(yè)務(wù)需求下,返回值使用limit減少返回的行數(shù),減少網(wǎng)絡(luò)數(shù)據(jù)傳輸時(shí)間和帶寬浪費(fèi),但是CPU消耗還是要的
【SQL語句-關(guān)鍵字】所有的SQL關(guān)鍵字用大寫,避免SQL語句重復(fù)編譯造成系統(tǒng)資源的浪費(fèi)
6、單表查詢:計(jì)數(shù)優(yōu)化,不要使用 count(列名)或 count(常量)來替代 count(*)
【單表查詢】不要使用 count(列名)或 count(常量)來替代 count(*)
【單表查詢】使用 ISNULL()來判斷是否為 NULL 值
count(*) count(1) count(id)的效率
SELECT COUNT(*) FROM test1; -- 0.749 0.752 0.750 你看,count(*)性能很好SELECT COUNT(1) FROM test1; -- 1.578 0.900 1.455SELECT COUNT(id) FROM test1; -- 0.740 0.763 0.751很多人為了統(tǒng)計(jì)記錄條數(shù),就使用count(1)和count(primary_key)而不是count() ,他們認(rèn)為這樣性能更好,其實(shí)這是一個(gè)誤區(qū)。對(duì)于有些場景,這樣做可能性能會(huì)更差,因?yàn)閿?shù)據(jù)庫對(duì)count()計(jì)數(shù)操作做了一些特別的優(yōu)化。
第一,定義不同
count(column) 和 count(),count(1)是一個(gè)完全不一樣的操作,所代表的意義也完全不一樣。
count(column) 是表示結(jié)果集中有多少個(gè)column字段不為空的記錄。
count(),count(1)是表示整個(gè)結(jié)果集有多少條記錄。
查詢盡量用具體的字段,而不是直接select * ;聚合函數(shù)不適用大量數(shù)據(jù)查詢過濾;
count()—會(huì)吧null空列也統(tǒng)計(jì)進(jìn)去
count(列)—只會(huì)統(tǒng)計(jì)非空字段
在mysql5.6之后,count()速度比count(1)和count(列) 更快
7、單表查詢:去重優(yōu)化,能用GROUP BY的就不用DISTINCT
使用GROUP BY去重比DISTINCT效率高。
SELECT DISTINCT OrderID FROM Details WHERE UnitPrice > 10用下面的語句替換:SELECT OrderID FROM Details WHERE UnitPrice > 10 GROUP BY OrderID8、單表查詢,LIMIT優(yōu)化,巧借索引優(yōu)化orderby排序
【單表查詢-limit】如果查詢中有LIMIT的話,LIMIT也會(huì)在排序之后應(yīng)用,所以即使需要返回較少的數(shù)據(jù),臨時(shí)表和需要排序的數(shù)據(jù)量仍然會(huì)非常大
單表查詢-優(yōu)化LIMIT分頁,使用索引優(yōu)化orderby排序,因?yàn)樗饕怯行虻?#xff0c;加上覆蓋索引
在系統(tǒng)中需要分頁的操作通常會(huì)使用limit加上偏移量的方法實(shí)現(xiàn),同時(shí)加上合適的order by 子句。如果有對(duì)應(yīng)的索引,通常效率會(huì)不錯(cuò),否則MySQL需要做大量的文件排序操作。
一個(gè)非常令人頭疼問題就是當(dāng)偏移量非常大的時(shí)候,例如可能是limit 10000,20這樣的查詢,這是mysql需要查詢10020條然后只返回最后20條,前面的10000條記錄都將被舍棄,這樣的代價(jià)很高。
優(yōu)化此類查詢的一個(gè)最簡單的方法是盡可能的使用索引覆蓋掃描,而不是查詢所有的列。然后根據(jù)需要做一次關(guān)聯(lián)操作再返回所需的列。對(duì)于偏移量很大的時(shí)候這樣做的效率會(huì)得到很大提升。
對(duì)于下面的查詢:
select id,title from collect limit 90000,10;
該語句存在的最大問題在于limit M,N中偏移量M太大(我們暫不考慮篩選字段上要不要添加索引的影響),導(dǎo)致每次查詢都要先從整個(gè)表中找到滿足條件 的前M條記錄,之后舍棄這M條記錄并從第M+1條記錄開始再依次找到N條滿足條件的記錄。如果表非常大,且篩選字段沒有合適的索引,且M特別大那么這樣的代價(jià)是非常高的。 試想,如我們下一次的查詢能從前一次查詢結(jié)束后標(biāo)記的位置開始查找,找到滿足條件的100條記錄,并記下下一次查詢應(yīng)該開始的位置,以便于下一次查詢能直接從該位置 開始,這樣就不必每次查詢都先從整個(gè)表中先找到滿足條件的前M條記錄,舍棄,在從M+1開始再找到100條滿足條件的記錄了。
方法一:慮篩選字段(title)上加索引
title字段加索引 (此效率如何未加驗(yàn)證)
方法二:先查詢出主鍵id值(優(yōu)化LIMIT分頁,使用索引優(yōu)化orderby排序)
select id,title from collect where id>=
(select id from collect order by id limit 90000,1) limit 10;
原理:先查詢出90000條數(shù)據(jù)對(duì)應(yīng)的主鍵id的值,然后直接通過該id的值直接查詢?cè)搃d后面的數(shù)據(jù)。
方法三:“關(guān)延遲聯(lián)”
如果這個(gè)表非常大,那么這個(gè)查詢可以改寫成如下的方式:
Select news.id, news.description from news
inner join (select id from news order by title limit 50000,5) as myNew using(id);
這里的“關(guān)延遲聯(lián)”將大大提升查詢的效率,它讓MySQL掃描盡可能少的頁面,獲取需要的記錄后再根據(jù)關(guān)聯(lián)列回原表查詢需要的所有列。這個(gè)技術(shù)也可以用在優(yōu)化關(guān)聯(lián)查詢中的limit。
方法四:建立復(fù)合索引 acct_id和create_time
select * from acct_trans_log WHERE acct_id = 3095 order by create_time desc limit 0,10
注意sql查詢慢的原因都是:引起filesort,所以一定要避免filesort
9、單表查詢:排序優(yōu)化,order by 字段加索引
索引數(shù)據(jù)實(shí)際上是有序的,如果我們的需要的數(shù)據(jù)和某個(gè)索引的順序一致,而且我們的查詢又通過這個(gè)索引來執(zhí)行,那么數(shù)據(jù)庫一般會(huì)省略排序操作,而直接將數(shù)據(jù)返回,因?yàn)閿?shù)據(jù)庫知道數(shù)據(jù)已經(jīng)滿足我們的排序需求了。
實(shí)際上,利用索引來優(yōu)化有排序需求的 SQL,是一個(gè)非常重要的優(yōu)化手段。
10、單表查詢:分組優(yōu)化,group by 調(diào)節(jié)順序?yàn)閺?fù)合索引順序
存在的問題:通常情況下,SQL 語句中的 GROUP BY 子句會(huì)導(dǎo)致數(shù)據(jù)庫不得不通過一個(gè)排序(SORT)操作來實(shí)現(xiàn)對(duì)數(shù)據(jù)的分組,而排序被認(rèn)為是一個(gè)比較耗費(fèi) CPU 和內(nèi)存的操作。實(shí)際上某些情況下,如果寫法得當(dāng),當(dāng)中的排序操作是可以避免的。
具體來說,在寫 GROUP BY 子句的時(shí)候,應(yīng)該考慮到數(shù)據(jù)庫中已經(jīng)存在的索引的情況。如果 GROUP BY 子句中所有的列恰好包括在某個(gè)索引的鍵(Key column)的范圍之內(nèi)而且是處于開始的位置,那么在寫 GROUP BY 子句的時(shí)候,就應(yīng)該按照該索引上鍵的先后順序來寫 GROUP BY 子句。
比如說有如下的 SQL 語句:
SELECT C2, C3, C1, AVG(C4)FROM T1GROUP BY C2, C3, C1一般情況下,GROUP BY C2, C3, C1這樣的寫法都會(huì)導(dǎo)致數(shù)據(jù)庫執(zhí)行一個(gè)排序操作,消耗CPU。但是,如果表 T1 上已經(jīng)存在一個(gè)索引 IX1(C1, C2, C3, C4), 這里注意到 GROUP BY 子句中引用到的列(C2,C3,C1)正好是索引 IX1 中的前三個(gè)鍵,那么就可以通過改變 GROUP BY 子句中列的順序的辦法來避免這個(gè)排序操作。
可以把 SQL 語句改寫為如下所示:
SELECT C1, C2, C3, AVG(C4)FROM T1GROUP BY C1, C2, C3通過這樣改變 GROUP BY 子句中列的順序使其與索引 IX1 中的鍵順序一致,數(shù)據(jù)庫就可以利用 IX1 來訪問其已經(jīng)排序的鍵值并直接返回進(jìn)行下一步操作,從而避免額外的排序操作,從而帶來查詢性能上的提高。
3.4.3 多表連接查詢 4點(diǎn)
1、多表連接,五個(gè):用 join 代替子查詢,連接字段類型一致,連接字段加索引,主表是小表,orderby和groupby字段在主表,否則無用
【多表連接-join代替子連接】通常情況下,子查詢的性能比較差,建議改造成JOIN寫法;
多表查詢:嵌套子查詢?nèi)绾蝺?yōu)化
嵌套子查詢的效率低的原因:執(zhí)行子查詢時(shí),MYSQL需要?jiǎng)?chuàng)建臨時(shí)表,查詢完畢后再刪除這些臨時(shí)表,多了一個(gè)創(chuàng)建和銷毀臨時(shí)表的過程
嵌套子查詢的優(yōu)化方案:
1、使用join,join比嵌套子查詢更高效,數(shù)據(jù)量較大時(shí),無需真正帶入不同參數(shù)循環(huán)迭代
2、拆分為多個(gè)查詢語句
多表查詢-分解關(guān)聯(lián)查詢
解釋:將一個(gè)大的查詢分解為多個(gè)小查詢是很有必要的。
很多高性能的應(yīng)用都會(huì)對(duì)關(guān)聯(lián)查詢進(jìn)行分解,就是可以對(duì)每一個(gè)表進(jìn)行一次單表查詢,然后將查詢結(jié)果在應(yīng)用程序中進(jìn)行關(guān)聯(lián),很多場景下這樣會(huì)更高效,例如:
SELECT * FROM tag
JOIN tag_post ON tag_id = tag.id
JOIN post ON tag_post.post_id = post.id
WHERE tag.tag = ‘mysql’;
分解為:
SELECT * FROM tag WHERE tag = ‘mysql’;
SELECT * FROM tag_post WHERE tag_id = 1234;
SELECT * FROM post WHERE post.id in (123,456,567);
【多表連接-連接字段CPU消耗一致】多表聯(lián)接查詢時(shí),關(guān)聯(lián)字段類型盡量一致,省去字段類型轉(zhuǎn)換的CPU消耗;
【多表連接-連接字段都要有索引】多表聯(lián)接查詢時(shí),關(guān)聯(lián)字段都要有索引;
【多表連接-主表是小表】多表連接查詢時(shí),把結(jié)果集小的表(注意,這里是指過濾后的結(jié)果集,不一定是全表數(shù)據(jù)量小的)作為驅(qū)動(dòng)表,解釋:小表全表掃描,用來比較大表。
【多表查詢-groupby orderby在主表上】多表查詢中,如果使用group by,order by,最好都能選擇驅(qū)動(dòng)表的列或者只選擇一個(gè)表上的列,否則無法排序和分組無法用到索引。
多表查詢,分析具體的SQL語句
1、兩個(gè)表選哪個(gè)為驅(qū)動(dòng)表,表面是可以以數(shù)據(jù)量的大小作為依據(jù),但是實(shí)際經(jīng)驗(yàn)最好交給mysql查詢優(yōu)化器自己去判斷。
例如: select * from a where id in (select id from b );
對(duì)于這條sql語句它的執(zhí)行計(jì)劃其實(shí)并不是先查詢出b表的所有id,然后再與a表的id進(jìn)行比較。
mysql內(nèi)部會(huì)將 in子查詢轉(zhuǎn)換成exists相關(guān)子查詢,所以它實(shí)際等同于這條sql語句:select * from a where exists(select * from b where b.id=a.id );
而exists相關(guān)子查詢的執(zhí)行原理是: 循環(huán)取出a表的每一條記錄與b表進(jìn)行比較,比較的條件是a.id=b.id . 看a表的每條記錄的id是否在b表存在,如果存在就行返回a表的這條記錄。
exists查詢有什么弊端?外面的a表無法使用索引,必須走全表掃描
由exists執(zhí)行原理可知,a表(外表)使用不了索引,必須全表掃描,因?yàn)槭悄胊表的數(shù)據(jù)到b表查。而且必須得使用a表的數(shù)據(jù)到b表中查(外表到里表中),順序是固定死的。
如何優(yōu)化?
加索引。但是由上面分析可知,要建索引只能在b表的id字段建,不能在a表的id上,mysql利用不上。
這樣優(yōu)化夠了嗎?還差一些。
由于exists查詢它的執(zhí)行計(jì)劃只能拿著a表的數(shù)據(jù)到b表查(外表到里表中),雖然可以在b表的id字段建索引來提高查詢效率。
但是并不能反過來拿著b表的數(shù)據(jù)到a表查,exists子查詢的查詢順序是固定死的。
為什么要反過來?
因?yàn)槭紫瓤梢钥隙ǖ氖欠催^來的結(jié)果也是一樣的。這樣就又引出了一個(gè)更細(xì)致的疑問:在雙方兩個(gè)表的id字段上都建有索引時(shí),到底是a表查b表的效率高,還是b表查a表的效率高?
該如何進(jìn)一步優(yōu)化?
把查詢修改成inner join連接查詢:select * from a inner join b on a.id=b.id; (但是僅此還不夠,接著往下看)
為什么不用left join 和 right join?
這時(shí)候表之間的連接的順序就被固定住了,比如左連接就是必須先查左表全表掃描,然后一條一條的到另外表去查詢,右連接同理。仍然不是最好的選擇。
為什么使用inner join就可以?
inner join中的兩張表,如: a inner join b,但實(shí)際執(zhí)行的順序是跟寫法的順序沒有半毛錢關(guān)系的,最終執(zhí)行也可能會(huì)是b連接a,順序不是固定死的。如果on條件字段有索引的情況下,同樣可以使用上索引。
那我們又怎么能知道a和b什么樣的執(zhí)行順序效率更高?
你不知道,我也不知道。誰知道?mysql自己知道。讓mysql自己去判斷(查詢優(yōu)化器)。具體表的連接順序和使用索引情況,mysql查詢優(yōu)化器會(huì)對(duì)每種情況做出成本評(píng)估,最終選擇最優(yōu)的那個(gè)做為執(zhí)行計(jì)劃。
在inner join的連接中,mysql會(huì)自己評(píng)估使用a表查b表的效率高還是b表查a表高,如果兩個(gè)表都建有索引的情況下,mysql同樣會(huì)評(píng)估使用a表?xiàng)l件字段上的索引效率高還是b表的。
利用explain字段查看執(zhí)行時(shí)運(yùn)用到的key(索引)
而我們要做的就是:把兩個(gè)表的連接條件的兩個(gè)字段都各自建立上索引,然后explain 一下,查看執(zhí)行計(jì)劃,看mysql到底利用了哪個(gè)索引,最后再把沒有使用索引的表的字段索引給去掉就行了。
錯(cuò)誤使用Left Join
left join的表在where中出現(xiàn),如
Table1 left join Table2 on Table1.C1=Table2.c1 where Table2.column1=XXX
實(shí)際上已經(jīng)變成了inner join
Left join會(huì)限制連接順序,而且where條件只能在連接后過濾,影響性能
2、多表查詢,很多時(shí)候用exists代替in是一個(gè)好的選擇(理由:雖然in與exists效率在不同場景效率有高有低,但not exists比not in的效率高)
select num from a where num in (select num from b);用下面的語句替換:select num from a where exists (select 1 from b where num=a.num)3、盡量避免向客戶端返回大數(shù)據(jù)量,應(yīng)該使用分頁
若從數(shù)據(jù)庫一次性返回的數(shù)據(jù)量過大,應(yīng)該考慮相應(yīng)需求是否合理,是否可以通過分頁等方法處理。
【多表查詢-分頁SQL語句】類似分頁功能的SQL,建議先用主鍵關(guān)聯(lián),然后返回結(jié)果集,效率會(huì)高很多;
分頁SQL優(yōu)化
分頁SQL可能包含order by,group by和多表join等操作,可以利用前面的優(yōu)化手段
后臺(tái)分頁取數(shù)SQL可以用cursor替換,分批fetch;使用分頁SQL多次取,性能差,數(shù)據(jù)一致性沒保證
4、用Union ALL代替OR(比較少用,一般都用OR)
SELECT * FROM test1 WHERE NAME = 'test1_1' OR NAME = 'test1_2'; -- 3.179 2.902 2.806(100萬數(shù)據(jù)測試) SELECT * FROM test1 WHERE NAME = 'test1_1'union allSELECT * FROM test1 WHERE NAME = 'test1_2'; -- 2.335 2.485 2.432(100萬數(shù)據(jù)測試)拆分復(fù)雜查詢,使用聯(lián)合查詢,可以用多條sql語句來實(shí)現(xiàn)一條查詢結(jié)果,提高查詢性能。
能用UNION ALL就不要用UNION
UNION ALL不執(zhí)行SELECT DISTINCT函數(shù),少一個(gè)步驟,這樣就會(huì)減少很多不必要的資源。
3.5 第五,通用
【通用】:
Mysql數(shù)據(jù)庫名和表名在 Windows 中是大小寫不敏感的,而在大多數(shù)類型的 Unix/Linux 系統(tǒng)中是大小寫敏感的。
MySQL大小寫敏感可以通過配置文件(Windows中的my.ini,Linux中的my.cnf)的lower_case_table_names參數(shù)來控制。
【通用】:數(shù)據(jù)庫的存儲(chǔ)三樣?xùn)|西----表結(jié)構(gòu),表數(shù)據(jù),索引
【通用 Mysql很少使用存儲(chǔ)引擎】Mysql很少采用類似SqlServer中大量使用的 “存儲(chǔ)過程”來實(shí)現(xiàn)業(yè)務(wù)邏輯(存儲(chǔ)過程也不是mysql的優(yōu)勢(shì));更多的是用 簡單的查詢,而把復(fù)雜的查詢業(yè)務(wù),直接在程序代碼中實(shí)現(xiàn),壓力轉(zhuǎn)義(java中有很多優(yōu)秀的orm框架)。 mysql雖然沒有sqlserver和oracle強(qiáng)大,但是它是開源免費(fèi),可以部署多臺(tái)sql服務(wù)器,實(shí)現(xiàn)分表分庫,集群,從而實(shí)現(xiàn)以量換性能。
四、小結(jié)
1、慢查詢和SQL調(diào)優(yōu)是一體的;
2、全文金手指,都是要記憶的。
總結(jié)
以上是生活随笔為你收集整理的必看,关于sql的慢查询及解决方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java简单拨号器界面
- 下一篇: jQuery移动端手机键盘输入车牌号代码