繁体   English   中英

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

[英]How can I improve query performance for 200+ million records

背景

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

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

CREATE TEMPORARY TABLE sgtins_tmp_table (`sgtin` varchar(255) primary key)
 engine=MEMORY;

存在查询

我使用此查询来查找sgtin是否存在。 这也是我找到的最快的查询。 对于50K 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;

解释计划

摘要

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

我会这样写你的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)sgtins(sgtin, client_id)索引。

我建议重提您的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)的数据选择性,建立索引(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;

观看演示

由于评论而被编辑

一种更理想的方法是使用固定表,该表将为您建立索引并使用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; 

观看演示

假设每个客户端有200M行,并且每个客户端不超过5万个sgtins,那么必须有超过4K的客户端吗?

仅对10个客户进行基准测试是有风险的。 在某些情况下,优化器会在使用索引和执行表扫描之间进行切换。 可能是这种情况。

因此,请说明最终目标; 我不想建议您如何更快地运行基准测试,而只是为了避免“实际”情况与建议配合使用。

另外,桩号列表是静态的吗? 您通过建议预先建立一个MEMORY表来暗示这一点。 但这似乎并不常见。 也许“真实”情况每次都被赋予了一组不同的sgtins。

因此,我将回答这个问题:

  • 2亿行
  • 桌子超过24GB
  • innodb_buffer_pool_size = 24G
  • 成千上万个不同的client_id值。 (只有10个时,Optimizer倾向于忽略索引并进行表扫描。)
  • 每个client_id的数千个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提供EXPLAIN数字,以下都是这两个查询的最佳解决方案:

WHERE client_id = 1234 AND stgin IN (..long list..)`
PRIMARY KEY(client_id, stgin)   -- in this order.

为什么?

  • 优化器很高兴将精力集中在client_id = constant并跳转到stgin列表。
  • 通过在PK中首先使用client_idSELECT所有活动将集中在表的一小部分。 这很重要,因为它将接触的块数限制为小于buffer_pool_size。
  • 从技术上讲,对于SELECT stgin... ,独立的INDEX(client_id, stgin)会更快,但是我不建议这样做,因为它是如此冗余,并且不会节省很多性能。

成本分析评论:

  • 它不考虑是否缓存了块。 使用HDD驱动器,这可以带来巨大的差异(10倍)。
  • 它并没有过多考虑索引与数据,也没有考虑索引+数据(例如在非覆盖二级索引中)
  • 它对价值的分布一无所知。 (除非使用具有直方图的MariaDB或MySQL 8.0)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM