目錄
Mysql優化(出自官方文件) - 第五篇
1 GROUP BY Optimization
通常來講,實現group by
的方式是建立一個臨時表,然後按照group by
的列插入到臨時表中,在進行後續處理,但是如果group by
的列均來自於同一個index
(唯一或者二級索引),那麼Mysql
會使用index
來進行group by
處理。關於索引的使用方式,主要有兩種:
Loose Index Scan
:將group by
操作和所有的range
一起來進行操作。Tight Index Scan
:首先進行range scan
,然後在對獲取到的結果進行分組。
Loose Index Scan
定義:不需要掃描
group by
的所有列來滿足where
條件,只需要考慮索引中的一部分來滿足要求。Loose Index Scan
需要滿足下面的條件:- 只針對一個表
group by
的列必須是索引leftmost
的列(如果沒有group by,distrinct
也可以,且distrinct
的列也必須為leftmost
),比如,在一個表(t1,t2,t3,t4)
上有索引(t1,t2,t3)
,此時group by
的列為(t1,t2)
或者(t1)
,均適用於Loose Index Scan
,如果是(t1,t3,t4)
,那麼將無法使用該優化技術。- 只支援
MIN,MAX
聚合函式,並且作用的物件都必須為同一個列,該列必須在索引裡面且在group by
語句中。 select
中不在group by
的列比較物件必須為常量,包括MIN
和MAX
- 索引中的列必須完全被索引,比如:一個
c1 varchar(20)
,索引不能為c1(10)
,必須為整個長度。
在EXPLAIN輸出裡面,如果採用了這種優化技術,那麼會顯示
Using index for group-by
下面的語句均使用這種優化技術(假設
t1
表(t1,t2,t3,t4)
,有索引(t1,t2,t3)
):SELECT c1, c2 FROM t1 GROUP BY c1, c2; SELECT DISTINCT c1, c2 FROM t1; SELECT c1, MIN(c2) FROM t1 GROUP BY c1; SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2; SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2; SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2; SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;
下面的語句不適用這種情況:
- 聚合函式只支援
MIN
和MAX
:
SELECT c1, SUM(c2) FROM t1 GROUP BY c1;
group by
的列必須為leftmost
:SELECT c1, c2 FROM t1 GROUP BY c2, c3;
select中剩餘的列(沒有被group by包含的)必須和一個常量進行比較,下面的c3不滿足:
SELECT c1, c3 FROM t1 GROUP BY c1, c2;
除了
MIN
和MAX
外,Loose Index Scan
也可以作用於其他形式的聚合函式,但是有如下限制:AVG, SUM
和COUNT
均可以支援,但是AVG, SUM
必須只有一個引數,COUNT
可以有多個引數- 不能有
GROUP BY
和DISTINCT
語句 - 文章開頭的限制依舊適用於這種情況。
假設t1表(t1,t2,t3,t4)有索引(t1,t2,t3),下面的語句也可以使用與Loose Index Scan:
SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1; SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;
Tight Index Scan
當Loose Index Scan無法適用時,此時如果適用於Tight Index Scan,Mysql依舊不會去建立一個臨時表,Tight Index Scan的定義為:如果有where條件,那麼會根據索引範圍直接進行掃描(index Scan),反之,會進行一個Full Index Scan。對於下面的語句,不適用於Loose Index Scan,但是依舊可以採用Tight Index Scan來進行分組:依舊假設t1表(t1,t2,t3,t4)有索引(t1,t2,t3):
- GROUP BY的列不是leftmost,但是進行Full Index Scan後,在對其進行過濾。
SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3; SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;
2 DISTINCT Optimization
由於DISTINCT和GROUP BY是可以相互轉換的,因此,適用於GROUP BY的情況也同樣適用於DISTINCT,比如下面的語句是等價的:
SELECT DISTINCT c1, c2, c3 FROM t1
WHERE c1 > const;
SELECT c1, c2, c3 FROM t1
WHERE c1 > const GROUP BY c1, c2, c3;
3 LIMIT Query Optimization
大多數時候,Mysql
也會對LIMIT row_count
語句(沒有HAVING
)進行優化,大致總結如下:
- 當
LIMIT
只有小部分行時,Mysql
會選擇用full table scan
,而不是使用索引 Mysql
掃描到LIMIT
限定的行數後就會停止掃描,如果有order by
就會停止排序,如果有distinct
也會停止掃描,此時,Mysql
可能只對表的大部分行進行了排序,所以會導致一個結果:帶LIMIT
和不帶LIMIT
的order by
的結果有機率不一致,該點下面會進行解釋。Mysql
預設不會儲存已經查詢出來的結果,使用SQL_CALC_FOUND_ROWS
,這樣子可以通過SELECT FOUND_ROWS()
來重複獲取計算出來的結果。- 如果需要使用臨時表,
Mysql
也會利用row_count
來計算所需要的空間。 如果使用了
LIMIT
,那麼優化器也有可能避免使用filesort
,而是使用記憶體方式進行排序。
關於為什麼帶LIMIT
和不帶LIMIT
可能結果不一樣的原因:
當order by
的列有多個重複列的時候,如果帶LIMIT
,Mysql
會進行部分排序,如果部分排序後發現數目已經滿足LIMIT
的row_count
,那麼就會停止排序,並將結果直接返回,而這個時候,有些資料還沒有得到排序,所以這就導致了全排序(不帶LIMIT
)和帶LIMIT
的結果不一致,主要體現在其他列上面,來看下面的例子:
order by
的是category
列,表的資料如下:
mysql> SELECT * FROM ratings ORDER BY category;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
| 2 | 3 | 5.0 |
| 7 | 3 | 2.7 |
+----+----------+--------+
限制LIMIT 5
的結果可能為:
mysql> SELECT * FROM ratings ORDER BY category LIMIT 5;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 4 | 2 | 3.5 |
| 3 | 2 | 3.7 |
| 6 | 2 | 3.5 |
+----+----------+--------+
注意結果的第三行和第四行,和初始是不一樣的,因為此時Mysql
可能只進行了部分排序,所以有機率出現這樣的結果。Mysql
在實現排序的時候,只會保證order by
列的順序,並不會保證其他列的順序。
4 Function Call Optimization
在Mysql裡面,Function
被分為兩種:確定性函式和非確定性函式。
確定性函式:給定一個值,多次呼叫會確定性的返回另外一個值
非確定性函式:給定一個值,多次呼叫可能返回不同的值,比如:RAND(), UUID()
假設有下面的表:
CREATE TABLE t (id INT NOT NULL PRIMARY KEY, col_a VARCHAR(100));
對於下面的兩種查詢:
SELECT * FROM t WHERE id = POW(1,2);
SELECT * FROM t WHERE id = FLOOR(1 + RAND() * 49);
解釋如下:
對於第一條語句:由於POW
產生一個固定的結果,所以Mysql會將其視為一個常量進行優化,有可能使用索引查詢來快速找到對應記錄。
對於第二條語句:由於RAND
會產生不同的結果,所以Mysql會對錶t的每一行進行where
判斷,相應的,就會進行全表掃描,因為需要RAND
來產生不同的結果。
綜上,如果貿然使用非確定性的函式,對Mysql效能可能會產生隱性的不良影響,可能帶來的影響有下面幾種情況:
- 非確定性函式由於無法產生固定的結果,所以優化器就沒辦法對其優化,比如:如果是常量,可以使用
index lookup
,但是由於非確定性函式的存在,就只能用table scan
的方式了。 InnoDB
中,非確定性函式可能會導致鎖由單行鎖升級為range-key lock
。- 在複製操作中,
update
中有非確定性函式可能是不安全的。
如果不得不用確定性函式,可以使用下面的方式進行優化:
將
RAND
的結果首先賦值給一個常量,然後查詢語句中直接使用該常量,這樣,Mysql就會把where
語句後面的值視為常量來進行優化,比如此時可以使用index lookup
。SET @keyval = FLOOR(1 + RAND() * 49); UPDATE t SET col_a = some_expr WHERE id = @keyval;
將非確定性函式的值放在
derived table
裡面,然後在where
語句裡面直接使用derived table
裡面的值,Mysql也可以使用相應的優化措施。UPDATE /*+ NO_MERGE(dt) */ t, (SELECT FLOOR(1 + RAND() * 49) AS r) AS dt SET col_a = some_expr WHERE id = dt.r;
有的時候,如果可以確定部分條件,那麼用
and
連線起來非確定性函式,這樣子可以導致非確定性函式的執行次數大大減少,比如下面的語句。SELECT * FROM t WHERE partial_key=5 AND some_column=RAND();
5 Avoiding Full Table Scans
在EXPLAIN
的輸出結果裡面,如果column
列顯示為ALL
,那麼說明Mysql
本次在進行全表掃描,為了避免全表掃描,而是讓Mysql
使用索引,可用下面的方式:
使用
ANALYZE TABLE tbl_name
來更新表索引的分佈性。使用
FORCE INDEX
命令告訴Mysql table scan
的效率要遠低於使用索引。SELECT * FROM t1, t2 FORCE INDEX (index_for_column) WHERE t1.col_name=t2.col_name;
在啟動Mysql的時候,
mysqld
命令加上--max-seeks-for-key=1000
或者說使用SET max_seeks_for_key=1000
, 該值設定的越低,那麼Mysql就更傾向於使用索引。