如何使用自联接优化此查询?

时间:2016-10-21 17:32:38

标签: mysql optimization indexing self-join sql-execution-plan

我有下表:

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;

这是一个非常大的表(1100万行),我真的需要优化以下自连接查询:

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子句中,并没有看到太大的区别。)

索引idx_bucket正确用于外部查询,但在执行EXPLAIN查询计划时,idx_test不用于内部子查询。相反,它也使用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输出中,我只看到attribute_id下用于此索引的used_key_parts?根据MySQL文档(B-Tree Index Characteristics),btree索引是这样的,“B树索引可以用于使用=,&gt;,&gt; =,&lt;,&lt的表达式中的列比较; =,或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索引的文档让我相信应该包括范围比较。

3 个答案:

答案 0 :(得分:0)

尝试使用

创建索引
 KEY idx_test2 (attribute_id, double_value, event_date)

答案 1 :(得分: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'

答案 2 :(得分: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)

这最终加快了查询速度80倍(使用嵌套集表示,执行并获取结果需要40多分钟,而使用邻接列表表示,只需28秒即可完成)。现在我需要进行范围扫描的唯一值可能是double_value和event_date。