2016-10-21 17 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; 

は、これは非常に大きなテーブル(12万行)であり、私は本当に、次の自己結合クエリを最適化する必要があります。

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' 

(私が持っていますの範囲検索を外側のwhere節に移動しようとしましたが、大きな違いは見られませんでした。)

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ツリー索引のドキュメントでは、範囲の比較を含める必要があると私は考えています。

+0

何ですか?なぜ私はそれを掲示するとすぐにdownvotedされましたか?私の質問にはあいまいさやあいまいさがありませんか?オイ! –

+0

理由はわかりませんが、クエリ形式を改善することができますが、多くのpplがjsonのexplain planを読むことはできません。 –

+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' 

そして、Iは

KEY idx_test (attribute_id, parent) 

Tを使用して、テーブルにインデックスを定義しました彼は結果的にクエリを80倍高速化しました(ネストされたセット表現では、結果を実行してフェッチするのに40 +分かかり、隣接リスト表現では完了までにわずか28秒かかりました)。範囲スキャンを実行するために必要な値は、おそらくdouble_valueとevent_dateだけです。

0

はあなたがINDEX(patient_sid, attribute_id)を必要とする

KEY idx_test2 (attribute_id, double_value, event_date) 
+0

私のクエリでdouble_valueの比較に厳密な '='を使用するとうまくいきますが、上記のような範囲で '> ='を指定すると、複合インデックスでこの列は使用されません私は 'use index'ヒントを指定します。これは、b-tree索引についてのあいまいな質問に戻ります。ドキュメントによると、 "Bツリーインデックスは、=、>、> =、<、<=、またはBETWEEN演算子を使用する式の列比較に使用できますが、場合。 –

+1

多分このヘルプMySQLのインデックス[** TIPS **](http://mysql.rjweb.org/doc.php/index_cookbook_mysql) –

+0

確かにそれは非常に有用です。それが私の特別なケースを解決するかどうかはわかりませんが、少なくともそれは良いスタートです。 –

0
  • でインデックスを作成してみてください。残念ながら、これはすべて約l2に役立ちます。

  • LEFTを削除すると、不要な値patient_sidが発生する可能性があります。

  • double_value >= 1.2に必ず「1.2」が含まれているとは思わないでください。浮動小数点値にはいくつかの奇妙な丸め問題があります。 (「1.2」がFLOATに入れた場合に頭に浮かぶ1の故障ケースは、その後、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。 " –

関連する問題