2014/6/6

SQL性能小測試得出的驚人結果:60%失敗

這篇文章放了將近2個月才仔細閱讀
結果:我勉強算答對一題
保留起來。以後隨時複習

=============================================================

http://blog.jobbole.com/60800/

本文由 伯樂在線 - sunbiaobiao 翻譯自 MarkusWinand。歡迎加入技術翻譯小組。轉載請參見文章末尾處的要求。


2011年,我開展了「3分鐘測試你對SQL性能知道多少?」的測試活動。其中包含五個問題,它們是這樣的:每個問題有一個query/index查詢,問你這樣是否正確使用了索引。至今, 這個測試已經成了 Use The Index, Luke網站上的一個熱點。這個測試已經被回答了28,000次。
提醒一下:也許你不想被我劇透,你可以提前自己測試一下自己。
儘管這個測試是為了教育,我很好奇自己是否可以從中找到一些規律,我認為可以的。當你看這些結果時,要記住幾點,第一,這些測試因為很出人意料才惹 人眼球,也就是說,有的測試看著性能很高,其實性能不高。有的反之。只有一個問題答案符合你的第一印象。很有意思的是,這個測試並不知道參與者是誰,所有 人都可以參與,為了獲得一個好的分數,你也可以再來一遍。要曉得這個測試不是為了對索引進行科學研究。然而,我認為結果仍可以給人一些啟示。
下面我對每個問題展示兩個不同的統計圖。第一,每個問題平均沒正確回答多少次。第二,對於MySQL, Oracle, PostgreSQL 和 SQL Server統計數據有什麼不同。也就是說,是否MySQL 使用者會比PostgreSQL 使用者更懂索引呢?我很幸運獲得這樣的統計數據,原因是不同的數據庫提供商有自己獨特的語法定義。像MySQL和PostgreSQL 中的 LIMIT  到了SQL Server中就成了 TOP。因此參與者開始時要選擇一種數據庫,問題是針對所選數據庫的。
問題一:WHERE語句中的函數
從性能上來看,下面的SQL語句是好的實踐嗎?
查詢出所有2012年的行:
1
2
3
4
5
CREATE INDEX tbl_idx ON tbl (date_column);
 
SELECT text, date_column
  FROM tbl
 WHERE TO_CHAR(date_column, 'YYYY') = '2012';
這個例子 SQL語句使用了Oracle和PostgreSQL  的特有函數,在MYSQL中這個問題就使用YEAR(date_column),在SQL SERVER中則為datepart(yyyy, date_column)。當然我可以使用EXTRACT(YEAR date_column),但我覺得還是使用通用一點的語法好一點。
參與者有兩個選項:
  •  好的實踐 ,沒有大的性能改進可以採用了
  •  壞的實踐,有大的性能改進可以採用
答案是「壞實踐」,原因是雖然在date_column上有索引,但 是在date_column字段上加了函數以後,索引就失效了。你如果不信,你可以看看一些可以證明我的結論的腳本和最後的解釋說明。詳細的解釋都在 Use The Index, Luke網站的相關頁面上。
如果你不知道在字段上加函數時怎麼吧索引的功能給抹殺了,很多人都和你一樣。只有2/3的人給出了正確答案。算上有些人選了兩次,有些人是蒙的。這樣說來差不多只有一半的人答對,無疑是很少的。我用下面這張圖強調一下
q3_tofefetal_20140212
這是我平時工作中最常見的一個問題,當你在VARCHAR 類型的字段上使用UPPERTRIM等函數時同樣會碰到這個問題。請記住,當你對WHERE語句中使用的字段加上函數的時候,它的索引功能就失去了作用。
儘管這個結果很令人失望——只比隨便碰對的概率高17%,但這都沒讓我感到驚奇。讓我驚奇的是在不同數據庫使用者中結果的不同。
          q1_bydb_20140212
實施上 MYSQL使用者只得到了55%的分數——就像純粹蒙一樣低。PostgreSQL 使用者卻獲得了83%的分數。
也許產生這個結果的原因是MYSQL不支持function-based indexes而Oracle 和 PostgreSQL支持。Function-based 索引允許你使用索引表達式像TO_CHAR(date_column, 『YYYY'),雖然對這個測試來說,這樣做不是推薦的解決方案。但僅僅是這個特性的存在讓Oracle 和 PostgreSQL使用者對這個問題更有意識。SQL Server提供了類似的特性,雖然不能直接使用索引表達式,但是你可以創建所謂的computed column,這個列是可以被索引的。
雖然以上可以解釋為什麼MySQL 使用者的效率比較低,但這不是藉口。不管支持function-based indexes與否,那個 query/index句子總之效率很低。很有效果的改進是不在索引字段上使用函數:
1
2
3
4
SELECT text, date_column
  FROM tbl
 WHERE date_column >= TO_DATE('2012-01-01', 'YYYY-MM-DD')
   AND date_column <  TO_DATE('2013-01-01', 'YYYY-MM-DD');
索引字段不必改變。這種解決方案很靈活,因為它支持廣泛的類型——星期或月份。這是我推薦的解決方案。
我很好奇,我想知道那些正確回答問題的人怎麼在function-based索引上考慮復合索引。我最好把這種回答認為是正確了一半。
問題二:索引過之後的TOP-N查詢
從性能上來看是好的實踐還是壞的實踐?
按時間遠近排行:
1
2
3
4
5
6
7
CREATE INDEX tbl_idx ON tbl (a, date_column);
 
SELECT id, a, date_column
  FROM tbl
 WHERE a = ?
 ORDER BY date_column DESC
 LIMIT 1;
注意,那個問號是個佔位符。因為我經常推薦開發者使用綁定變量。
參與者有兩個選項:
  •  好的實踐 ,沒有大的性能改進可以採用了
  •  壞的實踐,有大的性能改進可以採用
這個問題看著有性能危險,但其實不是。一般看來order by一定會對數據排序,然而這個索引,使你沒有沒有必要對整個數據集排序,所以它就像查詢唯一索引鍵一樣快。
正確率接近與 「隨便蒙」 ,我認為人們對這個問題基本上沒有概念。
             q2_total_20140212
這個結果讓人難以接受,  我看到人們平時建立緩存表,恰恰為了避免我們介紹這種查詢,經常被計劃任務填滿。有趣的是這種日常任務經常引起性能問題,因為它需要在很小的時間間隔內確認緩存表中是否存在新的數據。然而,正確的索引應該是你的第一選擇。
q2_bydb_20140212
這裡,我要提一下Oracle 數據庫使用者要特別注意一下這個技巧。到12c 版本的Oracle數據庫仍然沒有提供像LIMIT or TOP等便利的語法糖。你可以使用ROWNUM的偽式的數據列。
1
2
3
4
5
6
7
8
SELECT *
  FROM (
        SELECT id, date_column
          FROM tbl
         WHERE a = :a
         ORDER BY date_column DESC
       )
 WHERE rownum <= 1;
這個多餘的複雜度讓Oracle使用者得到了錯誤的結果,比「隨便蒙」對的概率還低。 
對於這個問題回饋的另一個爭論是如果包含ID列將允許 index-only scan, 儘管這是正確的,但我不認為不這樣做就是一個「壞實踐」。因為查詢的只有一行。index-only scan可以避免單表訪問,很多情況下你可以使用它提高性能,但一般情況下我認為這是一種過早優化,這是只是我的觀點。但這個爭論可以讓我們看到 PostgreSQL 使用者獲得最好的分數。PostgreSQL直到9.2版本才有index-only scans。在2012年九月才發佈這個特性。因此PostgreSQL 沒有掉入認為只有index-only scan才能提高性能的陷阱。
問題三:索引列的順序
從性能上來看是好的實踐還是壞的實踐
兩個查詢語句:
1
2
3
4
5
6
7
8
9
10
CREATE INDEX tbl_idx ON tbl (a, b);
 
SELECT id, a, b
  FROM tbl
 WHERE a = ?
   AND b = ?;
 
SELECT id, a, b
  FROM tbl
 WHERE b = ?;
參與者有兩個選項:
  •  好的實踐 ,沒有大的性能改進可以採用了
  •  壞的實踐,有大的性能改進可以採用
答案是壞實踐,因為第二個查詢語句沒有正確地使用索引。把索引列的順序改為(b, a)可以使兩個查詢語句都能使用索引從而獲得很高的性能。在b上再加一個索引,從而無緣無故的帶來了很大的性能開銷。不幸地是我看到很多人都這麼做。
結果是令人失望的,但是我已經猜到了。比「隨便蒙」只高12.5% 。
q3_tofefetal_20140212
這也是一個我每天都遇到的問題,人們就是不知道復合索引是怎麼工作的。
q2_bydb_20140212
不同數據庫的使用者的回答很接近,可能是因為(不同數據庫)沒有很大語法區別和的特性影響回答的結果。Oracle的不為人知的Skip Scan特性有很小的影響。通常來講index-only  scan 的意識可能有影響,但這次它的影響是讓參與者更有可能回答對問題。
總之,統計表明,一些數據庫的使用者比另一下更瞭解索引。有趣的是PostgreSQL 使用者第三次獲得最高分。
問題四:模糊查詢
從性能上來看是好的實踐還是壞的實踐?
查詢一個句子:
1
2
3
4
5
CREATE INDEX tbl_idx ON tbl (text);
 
SELECT id, text
  FROM tbl
 WHERE text LIKE '%TERM%';
我這次給出了不一樣的答案:
  • 銀彈 ,總是運行的很快
  • 噩夢,有性能危險
正確答案是噩夢因為匹配符中使用了前綴通配符,反之如果使用匹配符「TERM%」就會更有效率。大部分人都能回答對這個問題。我可以說大部分人還是知道LIKE 不是用來全文搜索的。
q4_total_20140212
這個與眾不同的結果各種數據庫使用者的正確率相差無幾。
q4_byergfewgfdb_20140212
這一次PostgreSQL 使用者不是那麼牛逼了。我們仔細審視一下PostgreSQL 面對的問題就知道為什麼了。
1
2
3
4
5
CREATE INDEX tbl_idx ON tbl (text varchar_pattern_ops);
 
SELECT id, text
  FROM tbl
 WHERE text LIKE '%TERM%';
注意我們對索引字段的補充修飾(varchar_pattern_ops),在PostgreSQL中這個操作符類使的索引對後綴通配符無效。我加 上這個是想知道人們是否意識到在模糊查詢是前綴通配符會帶來問題。沒有操作符類,它不工作有兩個原因:(1)前綴通配符;(2)沒有操作符類,我認為這是 顯然的。
問題五a  Index-only  scan
第五個問題有點棘手,因為在這個測試開始時,PostgresSQL不支持 index-only scans。 因此我稍微調整,兩組的這個問題不一樣。 MySQL, Oracle and SQL Server中是關於index-only  scan。另一個是針對PostgresSQL 使用者出的關於索引列的順序問題。我把結果都展示在這裡。先看關於index-only scans:的問題。
從第一個到第二個查詢性能會怎麼改變?
從一百萬行中選出一百行:
1
2
3
4
5
6
CREATE INDEX tab_idx ON tbl (a, date_column);
 
SELECT date_column, count(*)
  FROM tbl
 WHERE a = 123
 GROUP BY date_column;
從一百萬行中選出十行
1
2
3
4
5
SELECT date_column, count(*)
  FROM tbl
 WHERE a = 123
   AND b = 42
 GROUP BY date_column;
這個問題有點不同,因為我給了四個答案:
  • 查詢性能大體相同
  • 依賴數據的不同
  • 查詢會變很慢(影響>10%)
  • 查詢會變很快(影響>10%)
在我出這個測試的時候,我十分曉得五五分的答案沒有什麼意義,要在讓參與者快速抓住要點並回答和給出準確答案之間做權衡。
簡單來說,正確答案是查詢會變的很慢,因為原來的查詢使用了index-only scan,這個查詢只使用了索引中的數據就能給出答案而不需要到實際的表中獲取數據。第二個查詢需要檢查數據列B,而數據列B不在索引中,因此數據庫要花 費多餘的開銷到拿出候選的行來判斷是否符合條件,它要從表中取出100行,這正是第一個查詢中要返回的數據行數。因為有group by操作,估計要取出更多的數據行,會使查詢變的很慢。
因為有多個選項,總體分數明顯下降,掉到了比「隨便蒙」低39% or 14%。
q2_bydb_20140212
我會說有39%的參與者知道正確答案這個結論是錯誤的,它們雖然給出了正確答案,但是我估計有25% 的人是蒙的。
分開各種數據庫使用者後,結果更是無聊。
q5_by3db_20140212
但是,我們仍然要看一下人們是怎麼回答的:
q5_byanswer_20140212
我非常吃驚,「大體相同」 和 「依賴具體的數據」這兩個選項都獲得了25%的選擇——它們可能都是猜的。這是否表明一半的參與者只是在胡亂猜。還是因為這是最後一個問題,很多人都想快 點做完看看答案,恩,很有可能。然而正確答案「會變的很慢」獲得了38.8%的選擇,導致只有10.9%的人選擇「會變的很快」選項。
我的本意是把人誤導選擇「會變的很快」,因為後者數據量更少——只有使用了index-only scan的情況下會變得不同,但是我假設我得到這個結果是因為人們通常會認為很明顯的答案肯定是錯的。這樣的話,我想驗證多少人會知道index- only scan的本意根本沒有得到證明。
問題5b:索引列順序和範圍操作符
這個問題只是給PostgreSQL 使用者的。
從性能上來看是好的實踐還是壞的實踐?
查詢狀態的X並且不超過五年的實體。
1
2
3
4
5
6
7
8
CREATE INDEX tbl_idx ON tbl (date_column, state);
 
SELECT id, date_column, state
  FROM tbl
 WHERE date_column >= CURRENT_DATE - INTERVAL '5' YEAR
   AND state = 'X';
 
(365 rows)
數據分佈如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SELECT count(*)
  FROM tbl
 WHERE date_column >= CURRENT_DATE - INTERVAL '5' YEAR;
 
 count
-------
  1826
 
SELECT count(*)
  FROM tbl
 WHERE state = 'X';
 
 count
-------
 10000
參與者有兩個選項:
  •  好的實踐 ,沒有大的性能改進可以採用了。
  •  壞的實踐,有大的性能改進可以採用。
正確答案是「壞實踐」,因為索引的數據列的順序不對。通常的索引列排序是規律是,如果等號運算符放在左邊就經常有很高的性能,過濾之後,再使用範圍操作符也很有效率。然而,如果範圍操作符放在左邊,就會喪失索引的好處,之後的的索引列也不能高效率的使用。
像以上沒有修改的查詢語句,我們要在索引中找出1826個實體(它們都符合date_column 列的過濾),然後對它們進行state 列過濾。如果過濾順序改變一下,數據庫就使得兩次過濾都很有效,直接把要過濾的行數限制在了365 行內。
人們是這樣回答的:
 q2_bydb_20140212
等一下,竟然比隨便猜猜的正確概率還低,人們不僅對次沒有意識,而且大多數人都有了錯誤的理解。然而我得承認這個」大多數「是有水分的。當我運行這個例子時,快的不只是一倍,竟然加速了70%。
總體分數:多少人通過了測試?
單獨看每個例子很有趣,但是那不能讓你知道有多少人答對了5個題目,下面的圖可以告訴你。
correct_answers_given
最後,我想把這張圖歸結為一個數字:到底多少人通過了測試?
考慮到只有五個問題,並且每個問題只有兩個選項,公平的說,我想答對三個不足以說明你通過了測試,答對五個又明顯要求過高。答對四個通過測試,我覺得這樣界定是很明智的。使用這個定義,38.2%通過了測試。多說一句,隨便猜通過的概率為12.5%。

原文鏈接: MarkusWinand   翻譯: 伯樂在線 - sunbiaobiao
譯文鏈接: http://blog.jobbole.com/60800/
[ 轉載必須在正文中標註並保留原文鏈接、譯文鏈接和譯者等信息。]

沒有留言:

JPA+complex key+custom Query

  來源: https://www.cnblogs.com/520playboy/p/6512592.html   整個來說,就是有複合主鍵 然後要使用  public interface XxXXxx DAO extends CrudRepository<Tc...