1. 程式人生 > >《sql進階教程》之幾個效能優化建議

《sql進階教程》之幾個效能優化建議

本文是《sql進階教程》閱讀筆記,感興趣可以閱讀該書對應章節,這本適合有一定sql基礎的同學閱讀。另外作者《sql基礎教程》也值得一看

嚴格地優化查詢效能時,必須要了解所使用資料庫的功能特點。此外,查詢速度慢並不只是因為 SQL 語句本身,還可能是因為記憶體分配不佳、檔案結構不合理等其他原因。因此本節即將介紹的優化SQL 的方法未必能解決所有的效能問題,但是確實很多時候查詢效能不好的原因還是 SQL 的寫法不合理

一、使用高效的查詢

引數是子查詢時,使用 EXISTS 代替 IN;使用 EXISTS 時更快的原因有以下兩個。

-- 慢
SELECT *
FROM Class_A WHERE id IN (SELECT id FROM Class_B); -- 快 SELECT * FROM Class_A A WHERE EXISTS (SELECT * FROM Class_B B WHERE A.id = B.id);
  • 如果連線列(id )上建立了索引,那麼查詢 Class_B 時不用查實際的表,只需查索引就可以了。

  • 如果使用 EXISTS ,那麼只要查到一行資料滿足條件就會終止查詢,不用像使用 IN 時一樣掃描全表。在這一點上 NOT EXISTS 也一樣

當 IN 的引數是子查詢時,資料庫首先會執行子查詢,然後將結果儲存在一張臨時的工作表裡(內聯檢視),然後掃描整個檢視。很多情況下這種做法都非常耗費資源。使用 EXISTS

的話,資料庫不會生成臨時的工作表


要想改善 IN 的效能,除了使用 EXISTS ,還可以使用連線

-- 使用連線代替IN
SELECT A.id, A.name
FROM Class_A A INNER JOIN Class_B B ON A.id = B.id;

其他方式思路

select id from t where num in(1,2,3)  
對於連續的數值,能用 between 就不要用 in 了:  
select id from t where num between 1 and 3  

二、避免排序

會進行排序的代表性的運算有下面這些。

  • GROUP BY 子句
  • ORDER BY 子句
  • 聚合函式(SUMCOUNTAVGMAXMIN
  • DISTINCT
  • 集合運算子(UNIONINTERSECTEXCEPT
  • 視窗函式(RANKROW_NUMBER 等)

排序如果只在記憶體中進行,那麼還好;但是如果記憶體不足因而需要在硬碟上排序,那麼伴隨硬碟訪問聲,排序的效能也會急劇惡化; 儘量避免(或減少)無謂的排序


SQL 語言裡有 MAXMIN 兩個極值函,使用這兩個函式時都會進行排序。但是如果引數欄位上建有索引,則只需要掃描索引,不需要掃描整張表


2.1 在 GROUP BY 子句和 ORDER BY 子句中使用索引

一般來說,GROUP BY子句和 ORDER BY子句都會進行排序,來對行進行排列和替換。不過,通過指定帶索引的列作為 GROUP BY 和ORDER BY 的列,可以實現高速查詢。特別是,在一些資料庫中,如果操作物件的列上建立的是唯一索引,那麼排序過程本身都會被省略掉。


三、真的用到索引了嗎

3.1 索引的基本知識

-- 建立。單索引和組合索引
create index 索引名稱 on 表 (欄位)
create index 索引名稱 on 表 (欄位1,欄位2....;
-- 刪除
drop index '索引名稱'
-- 儲存索引的表; 可以查詢某一個具體的表的索引。
select * from pg_stat_user_indexes;
--所有的索引統計在 `pg_stat_user_indexes` 表中,可以通過表名查詢到具體的索引。
--可以通過索引分析索引使用情況。從而定位索引是否被高效使用。
--EXPLAIN  sql語句;   -- 查詢分析計劃,通過執行計劃可以迅速定位
  • 使用索引時,條件表示式的左側應該是原始欄位
-- 1.在索引欄位上進行運算
SELECT * FROM SomeTable
WHERE col_1 * 1.1 > 100;

-- 把運算的表示式放到查詢條件的右側,就能用到索引
-- WHERE col_1 > 100 / 1.1

--在查詢條件的左側使用函式時,也不能用到索引

SELECT * FROM SomeTable
WHERE SUBSTR(col_1, 1, 1) = 'a';
  • 使用 IS NULL 謂詞

索引欄位是不存在 NULL 的,所以指定 IS NULLIS NOT NULL 的話會使得索引無法使用,進而導致查詢效能低下

SELECT * FROM SomeTable
WHERE col_1 IS NULL;

-- 然而,如果需要使用類似 IS NOT NULL 的功能,又想用到索引,
-- 那麼可以使用下面的方法,假設“col_1”列的最小值是 1

--IS NOT NULL 的代替方案
SELECT *
FROM SomeTable
WHERE col_1 > 0;
-- 只要使用不等號並指定一個比最小值還小的數,就可以選出 col_1 中所有的值。
--因為 col_1 > NULL 的執行結果是unknown ,所以當“col_1”列的值為 NULL 的行不會被選擇
  • 下面這幾種否定形式不能用到索引
 <>
!=
NOT IN
-- 下面的 SQL 語句也會進行全表掃描。
SELECT *
FROM SomeTable
WHERE col_1 <> 100;
  • 使用 OR

在 col_1 和 col_2 上分別建立了不同的索引,或者建立了(col_1,col_2 )這樣的聯合索引時,如果使用 OR 連線條件,那麼要麼用不到索引,要麼用到了但是效率比 AND 要差很多

SELECT *
FROM SomeTable
WHERE col_1 > 100
OR col_2 = 'abc';
  • 進行預設的型別轉換
-- 預設的型別轉換不僅會增加額外的效能開銷,還會導致索引不可用,
--可以說是有百害而無一利;在需要型別轉換時顯式地進行型別轉換
× SELECT * FROM SomeTable WHERE col_1 = 10;SELECT * FROM SomeTable WHERE col_1 = '10';SELECT * FROM SomeTable WHERE col_1 = CAST(10, AS CHAR(2));

四、減少中間表

在 SQL 中,子查詢的結果會被看成一張新表,這張新表與原始表一樣,可以通過程式碼進行操作。這種高度的相似性使得 SQL 程式設計具有非常強的靈活性,但是如果不加限制地大量使用中間表,會導致查詢效能下降

頻繁使用中間表會帶來兩個問題,一是展開資料需要耗費記憶體資源,二是原始表中的索引不容易使用到(特別是聚合時)。因此,儘量減少中間表的使用也是提升效能的一個重要方法。

  • 靈活使用 HAVING 子句
SELECT *
FROM (SELECT sale_date, MAX(quantity) AS max_qty
FROM SalesHistory
GROUP BY sale_date) TMP ←----- 沒用的中間表
WHERE max_qty >= 10;

-- 對聚合結果指定篩選條件時不需要專門生成中間表,
-- 像下面這樣使用 HAVING 子句就可以。
SELECT sale_date, MAX(quantity)
FROM SalesHistory
GROUP BY sale_date
HAVING MAX(quantity) >= 10;
  • 需要對多個欄位使用 IN 謂詞時,將它們彙總到一處

SQL-92 中加入了行與行比較的功能。這樣一來,比較謂詞 = 、< 、>
和 IN 謂詞的引數就不能是標量值,而應是值列表

-- 這裡對多個欄位使用了 IN 謂詞,“id”列是主鍵。
SELECT id, state, city
FROM Addresses1 A1
WHERE state IN (SELECT state 
                  FROM Addresses2 A2 
				   WHERE A1.id = A2.id)
AND city IN (SELECT city 
               FROM Addresses2 A2 
              WHERE A1.id = A2.id);
-- 這段程式碼中用到了兩個子查詢


SELECT *
FROM Addresses1 A1
WHERE id || state || city
IN (SELECT id || state|| city
      FROM Addresses2 A2);     
-- 這樣一來,子查詢不用考慮關聯性,而且只執行一次就可以         

-- 如果所用的資料庫實現了行與行的比較,那麼我們也可以像下面這樣,
-- 在 IN 中寫多個欄位的組合。
SELECT *
FROM Addresses1 A1
WHERE (id, state, city)
IN (SELECT id, state, city
FROM Addresses2 A2);
      

合理地使用檢視

檢視是非常方便的工具,相信日常工作中很多人都在頻繁地使用。但是,如果沒有經過深入思考就定義複雜的檢視,可能會帶來巨大的效能問題。特別是檢視的定義語句中包含以下運算的時候,SQL 會非常低效,執行速度也會變得非常慢。

  • 聚合函式(AVG 、COUNT 、SUM 、MIN 、MAX )
  • 集合運算子(UNION 、*NTERSECT 、EXCEPT 等)

要格外注意避免在檢視中進行聚合操作後需要特別注意最近越來越多的資料庫為了解決檢視的這個缺點,實現了物化檢視(materialized view)等技術; 當檢視的定義變得複雜時,可以考慮使用一下。