如何使用自连接优化此查询?
How to optimize this query with a self-join?
我有以下 table:
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;
这是一个非常大的table(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 输出中,我只看到 used_key_parts
下的 attribute_id
用于此索引?根据 MySQL 文档 (B-Tree Index Characteristics),btree 索引是 "A B-tree index can be used for column comparisons in expressions that use the =, >, >=, <, <=, or BETWEEN operators."
"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 树索引的文档让我相信应该包括范围比较。
尝试使用
创建索引
KEY idx_test2 (attribute_id, double_value, event_date)
您需要 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'
解决方案最终在自连接中使用邻接 list/parent 子关系,而不是自连接的嵌套集表示:
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'
然后,我使用
在 table 上定义了一个索引
KEY idx_test (attribute_id, parent)
这最终将查询速度提高了 80 倍(使用嵌套集表示,执行和获取结果需要 40 多分钟,而使用邻接表表示,只需 28 秒即可完成).现在我需要进行范围扫描的唯一值可能是 double_value 和 event_date。
我有以下 table:
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;
这是一个非常大的table(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 输出中,我只看到 used_key_parts
下的 attribute_id
用于此索引?根据 MySQL 文档 (B-Tree Index Characteristics),btree 索引是 "A B-tree index can be used for column comparisons in expressions that use the =, >, >=, <, <=, or BETWEEN operators."
"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 树索引的文档让我相信应该包括范围比较。
尝试使用
创建索引 KEY idx_test2 (attribute_id, double_value, event_date)
您需要
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'
解决方案最终在自连接中使用邻接 list/parent 子关系,而不是自连接的嵌套集表示:
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'
然后,我使用
在 table 上定义了一个索引KEY idx_test (attribute_id, parent)
这最终将查询速度提高了 80 倍(使用嵌套集表示,执行和获取结果需要 40 多分钟,而使用邻接表表示,只需 28 秒即可完成).现在我需要进行范围扫描的唯一值可能是 double_value 和 event_date。