2017-10-20 132 views
1

我正在尋找一些有關MySQL表格上的索引如何工作的見解,因爲我遇到了一些我不明白的問題。一張桌子上沒有被使用的索引

讓我們開始與我一起工作的表:

mysql> SHOW CREATE TABLE channeldata\G 
*************************** 1. row *************************** 
     Table: channeldata 
Create Table: CREATE TABLE `channeldata` (
    `channel_id` smallint(3) unsigned NOT NULL, 
    `station_id` smallint(5) unsigned NOT NULL, 
    `time` datetime NOT NULL, 
    `reading` double NOT NULL DEFAULT '0', 
    `average` double NOT NULL DEFAULT '0', 
    `location_lat` double NOT NULL DEFAULT '0', 
    `location_lon` double NOT NULL DEFAULT '0', 
    `location_alt` double(8,3) DEFAULT '0.000', 
    `quality` smallint(3) unsigned DEFAULT '0', 
    PRIMARY KEY (`channel_id`,`station_id`,`time`), 
    KEY `composite3` (`station_id`,`channel_id`,`quality`) USING BTREE, 
    KEY `composite` (`channel_id`,`station_id`,`time`,`quality`) USING BTREE 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 
/*!50100 PARTITION BY RANGE (YEAR(time)) 
(PARTITION p0 VALUES LESS THAN (2001) ENGINE = MyISAM, 
PARTITION p1 VALUES LESS THAN (2002) ENGINE = MyISAM, 
PARTITION p2 VALUES LESS THAN (2003) ENGINE = MyISAM, 
PARTITION p3 VALUES LESS THAN (2004) ENGINE = MyISAM, 
PARTITION p4 VALUES LESS THAN (2005) ENGINE = MyISAM, 
PARTITION p5 VALUES LESS THAN (2006) ENGINE = MyISAM, 
PARTITION p6 VALUES LESS THAN (2007) ENGINE = MyISAM, 
PARTITION p7 VALUES LESS THAN (2008) ENGINE = MyISAM, 
PARTITION p8 VALUES LESS THAN (2009) ENGINE = MyISAM, 
PARTITION p9 VALUES LESS THAN (2010) ENGINE = MyISAM, 
PARTITION p10 VALUES LESS THAN (2011) ENGINE = MyISAM, 
PARTITION p11 VALUES LESS THAN (2012) ENGINE = MyISAM, 
PARTITION p12 VALUES LESS THAN (2013) ENGINE = MyISAM, 
PARTITION p13 VALUES LESS THAN (2014) ENGINE = MyISAM, 
PARTITION p14 VALUES LESS THAN (2015) ENGINE = MyISAM, 
PARTITION p15 VALUES LESS THAN (2016) ENGINE = MyISAM, 
PARTITION p16 VALUES LESS THAN (2017) ENGINE = MyISAM, 
PARTITION p17 VALUES LESS THAN (2018) ENGINE = MyISAM) */ 
1 row in set (0.00 sec) 

我運行查詢在2017年的「解讀」八月/九月/十月選擇數據通過一天均勻地分佈,並總是在10分鐘的邊界上(即10:10:00,10:20:00,10:30:00等)。從2017年5月起,每天「讀數」的數量相當一致,爲15.000。 P17分區總共有300多萬個讀數。

查詢我想一些幫助,看起來像這樣:

SELECT 
     ROUND(`a`.`average`,2) `average`, 
     UNIX_TIMESTAMP(`a`.`time`) * 1000 time, 
     `a`.`station_id` 
    FROM 
     `argus`.`channeldata` PARTITION (p17) `a` 
    WHERE 
     ((`a`.`station_id` = '3002' AND a.channel_id = '1') OR (`a`.`station_id` = '3004' AND a.channel_id = '1') OR [...] OR (`a`.`station_id` = '5052' AND a.channel_id = '1')) AND `a`.`time` BETWEEN "2017-08-17 00:00:00" AND "2017-10-13 23:59:59" AND `a`.`quality` IN('1') ORDER BY `a`.`time` ASC; 

下面是查詢格式清楚地顯示WHERE條件。

SELECT 
     ROUND(`a`.`average`,2) `average`, 
     UNIX_TIMESTAMP(`a`.`time`) * 1000 time, 
     `a`.`station_id` 
    FROM 
     `argus`.`channeldata` PARTITION (p17) `a` 
    WHERE 
     ( (`a`.`station_id` = '3002' AND a.channel_id = '1') 
      OR (`a`.`station_id` = '3004' AND a.channel_id = '1') 
      OR [...] 
      OR (`a`.`station_id` = '5052' AND a.channel_id = '1')) 
    AND `a`.`time` BETWEEN "2017-08-17 00:00:00" AND "2017-10-13 23:59:59" 
    AND `a`.`quality` IN('1') 
    ORDER BY `a`.`time` ASC; 

只是爲了得到一些指標,我開始選擇4周的讀數,5周等間隔。這些查詢完成的執行時間大約在4到5秒之間,隨着添加到區間的日數越多,執行時間就會略微增加。然而,突然間執行時間有所跳躍。在'BETWEEN'間隔增加一天幾乎將執行時間翻了近20秒。

我之前運行了&查詢內解釋和結果是我不明白。

隨着間隔爲BETWEEN "2017-08-18 00:00:00" AND "2017-10-13 23:59:59" EXPLAIN這個樣子的:

+----+-------------+-------+-------+------------------------------+---------+---------+------+--------+-----------------------------+ 
| id | select_type | table | type | possible_keys    | key  | key_len | ref | rows | Extra      | 
+----+-------------+-------+-------+------------------------------+---------+---------+------+--------+-----------------------------+ 
| 1 | SIMPLE  | a  | range | PRIMARY,composite3,composite | PRIMARY | 12  | NULL | 542026 | Using where; Using filesort | 
+----+-------------+-------+-------+------------------------------+---------+---------+------+--------+-----------------------------+ 
1 row in set (0.00 sec) 

以一天增加這BETWEEN "2017-08-17 00:00:00" AND "2017-10-13 23:59:59"看起來是這樣的:

+----+-------------+-------+------+------------------------------+------+---------+------+---------+-----------------------------+ 
| id | select_type | table | type | possible_keys    | key | key_len | ref | rows | Extra      | 
+----+-------------+-------+------+------------------------------+------+---------+------+---------+-----------------------------+ 
| 1 | SIMPLE  | a  | ALL | PRIMARY,composite3,composite | NULL | NULL | NULL | 3056618 | Using where; Using filesort | 
+----+-------------+-------+------+------------------------------+------+---------+------+---------+-----------------------------+ 
1 row in set (0.00 sec) 

有什麼事?爲什麼它突然不能使用主鍵/索引,而是搜索必須搜索整個300萬個分區的行的子集。在旁註中,間隔的確切位置並不重要。我可以通過提前一個月移動間隔來重新創建此問題。

如果有幫助,在執行時間「跳」之前返回的列是525644,當我加1額外的一天數爲535004.

+0

有多少百分比的數據具有質量= 1? –

回答

2

您的篩選標準是:

  1. 明確分區選擇
  2. quality
  3. 範圍掃描相等匹配上time
  4. 成對匹配上束和channel_id在一起。

處理標準2和3的索引是您所需要的。首先在索引中放入相等匹配列,然後放入範圍掃描列,然後將索引與查詢所需的其他列進行取整以得到covering index

索引爲(quality, time, station_id, channel_id, average)

爲什麼它的工作?查詢計劃員可以立即跳轉到索引的第一個合格行,因爲它知道quality和開始time必需。然後,它可以按順序掃描索引,進行配對匹配並檢索average列。 MySQL可以滿足來自索引的整個查詢,這可以節省大量的跳回表中以獲取信息,從而加快速度。

您已有索引(channel_id,station_id,time,quality)。您可能希望在創建新索引時刪除該索引,因爲它看起來似乎具有類似的用途。

爲什麼查詢計劃程序有時使用索引,有時不使用索引?這取決於很多事情,主要是查詢規劃者對於使用索引執行較少工作還是僅掃描表進行估計。索引和列包含基數的估計值 - 數據項中不同值的數量。這些基數是估計值,有時候相當不準確。你有分區:這可能會誘使查詢規劃者以某種方式限制其選擇。查詢計劃人員無法弄清楚要做什麼的後備方法是獲得:全表掃描。

在您的問題中提到的索引已經需要相當費力的索引掃描來滿足查詢;我想查詢計劃員在更改日期戳範圍時切換到全表掃描策略。對於運行基於DBMS的軟件的人來說,這是一個麻煩:隨着應用程序的增長,有時查詢規劃者突然轉向一個新的效率較低的計劃。您需要保持突然的性能變化並添加索引。

專業提示:詢問爲什麼關於查詢規劃師的選擇通常是一個沒有成果的企業,而不是建立一個更好的索引。 (除非你的開發工作是在查詢計劃器上工作。)

我提出了一個五列索引。您的查詢使用四列進行過濾,然後使用最後一列顯示結果。在索引中包含所有五列意味着MySQL不必返回主表中索引找到的各行。它可以單獨滿足來自索引的查詢,這意味着它可以從海量存儲中順序讀取索引。在傳統的旋轉硬盤驅動器上,這意味着讀取磁頭不必爲了滿足查詢而從索引到表格來回查詢。它快得多。它被稱爲covering index

專業提示:使用BETWEEN作爲datestamp範圍是一個錯誤。代替使用

WHERE time BETWEEN '2017-08-17 00:00:00' AND '2017-10-13 23:59:59' 

使用此。在範圍的末尾更精確。它仍然得到範圍掃描。

WHERE time >= '2017-08-17' 
    AND time < '2017-10-13' + INTERVAL 1 DAY 
+0

非常有魅力,thx。我想知道爲什麼MySQL決定停止在我原來的問題中使用現有的索引。我確實在某個地方看過,當它需要檢查的行大約佔總數的30%時,它突然停止使用索引,但我不知道這是否屬實。而且,爲什麼在WHERE子句中沒有使用平均值時,平均值會添加到索引中? – Lieuwe

+0

請參閱我的編輯。 –

1

優化有兩種方式在一定範圍內進行索引查詢:

選項1,使用索引:在項目的開始

  1. 河段入索引。
  2. 向前掃描直到範圍結束。篩選出不符合其他WHERE條件的行。
  3. 對於每個項目都會覆蓋數據以獲取所需的其他列。這是一個隨機讀入磁盤 - 可能沒有緩存等。

選項2,忽略索引並掃描數據。

  1. 掃描數據中的所有行,忽略任何與WHERE標準不匹配的行。

做一個方法和做另一個方法之間的截斷取決於大量的統計數據等。它通常在表的10%和30%之間。你注意到邊界有一個很大的跳躍;這是因爲統計不是「完美」的。這種跳躍可以是更好的或更糟糕的。

附註。一旦你有奧利的更好的索引,分區購買你沒有表現。事實上,它可能會降低查詢速度。

DOUBLE(8字節)爲lat/lng/alt是矯枉過正。見my representation choices

DOUBLE(8,3)(還有8個字節)更差;請勿在FLOATDOUBLE上使用(m,n)

平均數的平均值在數學上不正確。考慮保留一筆錢和一個計數,然後計算SUM(sum)/SUM(count)以得到一個合適的AVG

想要獲得每週結果10倍的速度?在彙總表中構建和維護日常計數和總和。那會使數據縮小1/144。然後通過彙總總和等來報告A discussion

+0

感謝您的反饋,不幸的是,表格和檢索不是我的。使用的時間間隔是任意的,可以是2天,也可以是3個月。平均值實際上是在數據插入時計算的(它是包括當前數據在內的最後X個讀數的平均值)並且是正確的 - 他們只是需要將它舍入到我估計的2位小數。你確定分區沒有任何區別嗎?引用的例子是在一臺測試機器上,在現有的服務器上,我們每年有數百萬的讀數可以追溯到二十年(如果間隔在分區邊界上,我顯然會說明這一點)。 – Lieuwe

相關問題