背景
我有一个MySQL测试环境,其中的表包含2亿多行。在此表上必须执行两种类型的查询;
client_id
和一个sgtin
的列表,它们最多可以容纳
50.000个项目,我需要知道表中存在哪些sgtin
。client_id
和一个sgtin
的列表,它们最多可以容纳
50.000项,我需要提取整行。 (商店,gtin ...)单个“ client_id”表的记录可以增长到200+百万条。
测试环境
至强E3-1545M / 32GB RAM / SSD。 InnoDB缓冲池24 GB。 (生产将是具有192GB RAM的更大服务器)
表格
CREATE TABLE `sgtins` (
`client_id` INT UNSIGNED NOT NULL,
`sgtin` varchar(255) NOT NULL,
`store` varchar(255) NOT NULL,
`gtin` varchar(255) NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX (`client_id`, `store`, `sgtin`),
INDEX (`client_id`),
PRIMARY KEY (`client_id`,`sgtin`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
测试
首先,我生成了随机的sgtin值,这些值分布在10个“ client_id”上,以用2亿行填充表格。
我创建了一个基准工具,该工具可执行我尝试过的各种查询。另外,我还使用了解释计划来找出最佳性能。对于每次测试,该工具都会从我用来填充数据库的数据中读取新的随机数据。为了确保每个查询都不同。
对于这篇文章,我将使用28 sgtin
s。
临时表
CREATE TEMPORARY TABLE sgtins_tmp_table (`sgtin` varchar(255) primary key)
engine=MEMORY;
现有查询
我使用此查询来查找sgtin
是否存在。这也是我找到的最快的查询。对于5万sgtin
秒,此查询将花费3到9秒。
-- cost = 17 for 28 sgtins loaded in the temp table.
SELECT sgtin
FROM sgtins_tmp_table
WHERE EXISTS
(SELECT sgtin FROM sgtins
WHERE sgtins.client_id = 4
AND sgtins.sgtin = sgtins_tmp_table.sgtin);
选择查询
-- cost = 50.60 for 28 sgtins loaded in the temp table. 50K not usable.
SELECT sgtins.sgtin, sgtins.store, sgtins.timestamp
FROM sgtins_tmp_table, sgtins
WHERE sgtins.client_id = 4
AND sgtins_tmp_table.sgtin = sgtins.sgtin;
-- cost = 64 for 28 sgtins loaded in the temp table.
SELECT sgtins.sgtin, sgtins.store, sgtins.timestamp
FROM sgtins
WHERE sgtins.client_id = 4
AND sgtins.sgtin IN ( SELECT sgtins_tmp_table.sgtin
FROM sgtins_tmp_table);
-- cost = 50.60 for 28 sgtins loaded in the temp table.
SELECT sgtins_tmp_table.epc, sgtins.store
FROM sgtins_tmp_table, sgtins
WHERE exists (SELECT organization_id, sgtin FROM sgtins WHERE client_id = 4 AND sgtins.sgtin = sgtins_tmp_table.sgtin)
AND sgtins.client_id = 4
AND sgtins_tmp_table.sgtin = sgtins.sgtin;
摘要
存在查询可用,但选择速度较慢。我该怎么办?并欢迎提出任何建议:)
答案 0 :(得分:2)
我会这样写您的exists
查询:
SELECT stt.sgtin
FROM sgtins_tmp_table stt
WHERE EXISTS (SELECT 1
FROM sgtins s
WHERE s.client_id = 4 AND
s.sgtin = stt.sgtin
);
对于此查询,您需要sgtins(sgtin, client_id)
上的索引。
答案 1 :(得分:1)
我建议重新编写您的EXISTS
SQL,因为相关的子查询在大多数情况下往往会严重优化。
建议的查询将改为使用INNER JOIN
。
SELECT filter.sgtin
FROM (SELECT '<value>' AS sgtin UNION ALL SELECT '<value>' ..) AS filter
INNER JOIN sgtins ON filter.sgtin = sgtins.sgtin WHERE sgtins.client_id = 4
这很有可能比使用临时表更快。
但是您正在处理50K值,因此我有理由直接从临时表中使用动态SQL生成所需的派生表SQL。
就像我在聊天中建议的一样。
根据数据的选择性(实际上还不清楚),建立索引(sgtins, client_id)
很可能更有意义。
由于该索引可能会使您的相关子查询更快。
查询
# Maybe also needed to be changed with 50 K
# SET SESSION max_allowed_packet = ??;
# needed for GROUP_CONCAT as if defualts to only 1024
SET SESSION group_concat_max_len = @@max_allowed_packet;
SET @UNION_SQL = NULL;
SELECT
CONCAT(
'SELECT '
, GROUP_CONCAT(
CONCAT("'", sgtins_tmp_table.sgtin,"'", ' AS sgtin')
SEPARATOR ' UNION ALL SELECT '
)
)
FROM
sgtins_tmp_table
INTO
@UNION_SQL;
SET @SQL = CONCAT("
SELECT filter.sgtin
FROM (",@UNION_SQL,") AS filter
INNER JOIN sgtins ON filter.sgtin = sgtins.sgtin WHERE sgtins.client_id = 4
");
PREPARE q FROM @SQL;
EXECUTE q;
请参阅demo
由于评论而编辑
更理想的方法是使用固定的表,该表将为您建立索引并使用CONNECTION_ID()
来分隔搜索值。
CREATE TABLE sgtins_filter (
connection_id INT
, sgtin varchar(255) NOT NULL
, INDEX(connection_id, sgtin)
);
然后,您可以简单地在两个表之间连接
SELECT sgtins_filter.sgtin
FROM sgtins_filter
INNER JOIN sgtins
ON
sgtins_filter.sgtin = sgtins.sgtin
AND
sgtins_filter.connection_id = CONNECTION_ID()
AND
sgtins.client_id = 4;
请参阅demo
答案 2 :(得分:1)
假设每个客户有2亿行,并且每个客户端最多不超过5万个sgtins,那么必须有超过4K个客户端吗?
仅对10个客户进行基准测试是有风险的。在某些情况下,优化器会在使用索引和执行表扫描之间进行切换。可能是这种情况。
因此,请说明最终目标;我不想建议您如何更快地运行基准测试,而只是为了避免“真实”情况与建议配合使用。
而且,桩号列表是静态的吗?您通过建议预先建立MEMORY
表来暗示这一点。但这似乎并不常见。也许“真实”情况每次都会被赋予一组不同的sgtins。
所以,我将回答这个问题:
client_id
值。 (优化器只有10个,很容易忽略索引并进行表扫描。)stgin
值(client_id, stgin)
对是唯一的SELECT stgin FROM t WHERE client_id = 1234 AND stgin IN (..long list..)
的东西SELECT * FROM t WHERE client_id = 1234 AND stgin IN (..long list..)
的东西不管EXPLAIN
提供了多少数字,以下都是这两个查询的最佳解决方案:
WHERE client_id = 1234 AND stgin IN (..long list..)`
PRIMARY KEY(client_id, stgin) -- in this order.
为什么?
client_id = constant
并跳转到清单列表。client_id
first 放在首位,SELECT
的所有活动都将集中在表格的一小部分。这很重要,因为它将接触的块数限制为小于buffer_pool_size。INDEX(client_id, stgin)
对于SELECT stgin...
会更快,但我不建议这样做,因为它是如此冗余,并且不会节省很多性能。费用分析评论: