2016-10-21 23 views
-2

我有如下表:如何使用自連接優化此查詢?

CREATE TABLE lab_data (
    id int(11) NOT NULL, 
    patient_sid int(11) DEFAULT NULL, 
    double_value double DEFAULT NULL, 
    string_value varchar(7) DEFAULT NULL, 
    data_type_id int(11) DEFAULT NULL, 
    event_date datetime DEFAULT NULL, 
    attribute_id int(11) DEFAULT NULL, 
    lft int(11) DEFAULT NULL, 
    rgt int(11) DEFAULT NULL, 
    parent int(11) DEFAULT NULL, 
    num_children int(11) DEFAULT NULL, 
    PRIMARY KEY (id), 
    KEY idx_bucket (attribute_id,string_value), 
    KEY idx_test (attribute_id,double_value,event_date,patient_id,lft,rgt) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

這是一個非常大的表(11萬行),我真的很需要優化以下自連接查詢:

SELECT distinct(patient_sid) as patient_sid 
FROM lab_data l1 
LEFT JOIN (SELECT patient_sid, lft, rgt 
      FROM lab_data 
      WHERE attribute_id = 36 AND double_value >= 1.2 AND event_date >= '1776-01-01' 
     ) AS l2 
ON l1. patient_sid = l2.patient_sid AND l1.lft >= l2.lft AND l1.rgt <= l2.rgt 
WHERE l1.attribute_id = 33 AND l1.string_value = '2160-0' 

(我有嘗試將AND l1.lft >= l2.lft AND l1.rgt <= l2.rgt的範圍搜索移動到外部where子句中,但沒有看到太大的差別。)

index,idx_bucket正確用於外部查詢,但idx_test未用於內部子查詢wh我做一個EXPLAIN查詢計劃。相反,它也使用idx_bucket。

# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra 
'1', 'SIMPLE', 'l1', NULL, 'ref', 'idx_bucket,idx_test', 'idx_bucket', '29', 'const,const', '517298', '100.00', 'Using temporary' 
'1', 'SIMPLE', 'lab_data', NULL, 'ref', 'idx_bucket,idx_test', 'idx_bucket', '5', 'const', '13657', '100.00', 'Using where; Distinct' 

如果我迫使內子查詢使用idx_test,我得到以下查詢計劃:

# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra 
'1', 'SIMPLE', 'l1', NULL, 'ref', 'idx_bucket,idx_test', 'idx_bucket', '29', 'const,const', '517298', '100.00', 'Using temporary' 
'1', 'SIMPLE', 'lab_data', NULL, 'ref', 'idx_test', 'idx_test', '5', 'const', '21808', '100.00', 'Using where; Distinct' 

而且從JSON輸出,我只看到used_key_partsattribute_id用於該指數?根據MySQL文檔(B-Tree Index Characteristics),btree索引是這樣的:「B樹索引可用於表達式中使用=,>,> =,<,< =或BETWEEN運算符的列比較。

"table": { 
    "table_name": "lab_data", 
    "access_type": "ref", 
    "possible_keys": [ 
    "idx_test" 
    ], 
    "key": "idx_test", 
    "used_key_parts": [ 
    "attribute_id" 
    ], 
    "key_length": "5", 
    "ref": [ 
    "const" 
    ], 
    "rows_examined_per_scan": 8898041, 
    "rows_produced_per_join": 988473, 
    "filtered": "11.11", 
    "index_condition": "((`ns_large2_2016`.`lab_data`.`double_value` >= 1.2) and (`ns_large2_2016`.`lab_data`.`event_date` >= '1776-01-01'))", 
    "cost_info": { 
    "read_cost": "339069.00", 
    "eval_cost": "197694.69", 
    "prefix_cost": "2118677.20", 
    "data_read_per_join": "82M" 
    }, 
    "used_columns": [ 
    "patient_sid", 
    "double_value", 
    "event_date", 
    "attribute_id", 
    "lft", 
    "rgt" 
    ] 

我誤解了什麼used_key_parts是什麼?我假設這些是正在使用的索引的列。 b-tree索引的文檔使我相信應該包括範圍比較。

+0

什麼?爲什麼只要我發佈它,就會降低它的價值?在我的問題中,我沒有看到任何模糊或模棱兩可的情況? Oy公司! –

+0

不知道爲什麼,但你可以改善查詢格式,並沒有多少人可以在JSON中讀取解釋計劃。 –

+0

欣賞反饋。有時候會有人想知道這裏發生了什麼! (我得到了查詢,並正在解釋計劃...) –

回答

0

該解決方案最終使用在一個鄰接表/父子關係是自連接,相對於的嵌套組表示自聯接:

SELECT distinct(patient_sid) as patient_sid 
FROM lab_data l1 
LEFT JOIN (SELECT parent 
      FROM lab_data 
      WHERE attribute_id = 36 AND double_value >= 1.2 AND event_date >= '1776-01-01' 
     ) AS l2 
ON l1.id = l2.parent 
WHERE l1.attribute_id = 33 AND l1.string_value = '2160-0' 

然後,我使用定義

KEY idx_test (attribute_id, parent) 

T於該表中的索引他最終加快了80倍的查詢速度(使用嵌套集合表示,執行和獲取結果需要40多分鐘,而使用鄰接列表表示則只需28秒即可完成)。現在我需要進行範圍掃描的唯一值可能是double_value和event_date。

0

嘗試用

KEY idx_test2 (attribute_id, double_value, event_date) 
+0

如果我在查詢中對double_value比較使用了一個嚴格的'=',那麼它的效果很好,但是如上面給出的範圍使用'> =',那麼它不會在複合索引中使用此列,即使我指定了'使用索引'提示。這回到了我對b-tree索引的模糊問題。根據文檔,「B樹索引可用於使用=,>,> =,<,<=或BETWEEN運算符...」的表達式中的列比較...但是,它似乎不是案件。 –

+1

也許這有助於MySQL索引[** TIPS **](http://mysql.rjweb.org/doc.php/index_cookbook_mysql) –

+0

事實上它非常有幫助。不知道它解決了我的特殊情況,但它至少是一個好的開始。 –

0
  • 創建索引你需要INDEX(patient_sid, attribute_id)。不幸的是,這是關於所有對l2有用的信息。

  • 刪除LEFT - 它可能會導致額外的patient_sid值,你不想要的。

  • 不要指望double_value >= 1.2必然包含「1.2」。浮點值有一些奇怪的四捨五入問題。 (出現在腦海的一個失敗案例是,如果「1.2」放入FLOAT,然後轉移到一個DOUBLE。)

  • DISTINCT(x) AS y可能發生的工作,但它沒有被解析你所期望的方式。 DISTINCT不是一個函數。說SELECT DISTINCT l1.patient_sid FROM ...

  • 看看下面的工作;它可能會更快:

    SELECT l1.patient_sid FROM lab_data l1 JOIN lab_data l2 ON l1.patient_sid = l2.patient_sid AND l1.lft >= l2.lft AND l1.rgt <= l2.rgt WHERE l1.attribute_id = 33 AND l1.string_value = '2160-0' AND l2.attribute_id = 36 AND l2.double_value >= 1.2 AND l2.event_date >= '1776-01-01'

+0

我曾嘗試過這種形式,沒有成功。請參閱[使用覆蓋索引優化查詢](http://stackoverflow.com/questions/37125283/optimization-of-query-using-covering-indices) –

+0

另外,這些查詢正在由SQLAlchemy ORM,所以使用DISTINCT必須作爲一個函數而不是一組選定的列。 –

+0

那麼,MySQL不會那樣工作:'SELECT DISTINCT(a),b FROM ...'和'SELECT DISTINCTROW a,b FROM ...'是一樣的。 「ALL和DISTINCT選項指定是否應該返回重複的行,ALL(缺省值)指定應該返回所有匹配的行,包括重複項,DISTINCT指定從結果集中刪除重複的行...... DISTINCTROW是DISTINCT「。 –