Mar*_*ers 8 mysql sql temp-tables database-performance
背景
我有一个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;
Run Code Online (Sandbox Code Playgroud)
测验
首先,我生成了随机的sgtin值,这些值分布在10个“ client_id”上,以用2亿行填充表格。
我创建了一个基准工具,该工具可以执行尝试的各种查询。另外,我还使用了解释计划来找出最佳性能。对于每次测试,该工具都会从我用来填充数据库的数据中读取新的随机数据。确保每个查询都是不同的。
对于这篇文章,我将使用28 sgtins。
临时表
CREATE TEMPORARY TABLE sgtins_tmp_table (`sgtin` varchar(255) primary key)
engine=MEMORY;
Run Code Online (Sandbox Code Playgroud)
存在查询
我使用此查询来查找sgtins是否存在。这也是我找到的最快的查询。对于50K sgtins,此查询将花费3到9秒。
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;
Run Code Online (Sandbox Code Playgroud)
选择查询
CREATE TEMPORARY TABLE sgtins_tmp_table (`sgtin` varchar(255) primary key)
engine=MEMORY;
Run Code Online (Sandbox Code Playgroud)
-- 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);
Run Code Online (Sandbox Code Playgroud)
-- 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;
Run Code Online (Sandbox Code Playgroud)
摘要
存在查询是可用的,但选择速度很慢。我该怎么办?并欢迎任何建议:)
我建议重写你的EXISTSSQL,因为相关子查询在大多数情况下往往优化得很差。
建议的查询是使用 aINNER 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
Run Code Online (Sandbox Code Playgroud)
这很可能比使用临时表更快。
但是您正在处理 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;
Run Code Online (Sandbox Code Playgroud)
看演示
由于评论而编辑
更理想的方法是使用固定表,您可以将其索引并用于CONNECTION_ID()分隔搜索值。
CREATE TABLE sgtins_filter (
connection_id INT
, sgtin varchar(255) NOT NULL
, INDEX(connection_id, sgtin)
);
Run Code Online (Sandbox Code Playgroud)
然后你可以简单地在两个表之间加入
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;
Run Code Online (Sandbox Code Playgroud)
看演示