如何提高200+百万条记录的查询性能

Mar*_*ers 8 mysql sql temp-tables database-performance

背景

我有一个MySQL测试环境,其中的表包含2亿多行。在此表上必须执行两种类型的查询;

  1. 是否存在某些行。给定一个client_id和的列表,sgtin最多可容纳50.000个项目,我需要知道sgtin表中存在哪些。
  2. 选择那些行。给定一个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)

解释计划

摘要

存在查询是可用的,但选择速度很慢。我该怎么办?并欢迎任何建议:)

Ray*_*and 1

我建议重写你的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)

演示