【问题标题】:selecting million rows is slow even for simple select statement即使对于简单的选择语句,选择百万行也很慢
【发布时间】:2015-02-17 19:03:56
【问题描述】:

我有一个带分区的简单表(范围分区数为 10)

CREATE TABLE `document_key_points` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `key_point_id` int(11) DEFAULT NULL,
  `data_date` date DEFAULT NULL,
  `data_decimal` decimal(22,6) DEFAULT '0.000000',
  `data_boolean` tinyint(1) DEFAULT NULL,
  `document_id` int(11) DEFAULT NULL,
  `data_integer` int(11) DEFAULT NULL,
  `is_deleted` tinyint(1) DEFAULT '0',
  `data_string` text,
  `created_at` datetime DEFAULT NULL,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   PRIMARY KEY (`id`,`key_point_id`),
  KEY `data_integer` (`data_integer`),
  KEY `document_id` (`document_id`),
  KEY `key_point_id` (`key_point_id`),
  KEY `data_boolean` (`data_boolean`),
  KEY `data_decimal` (`data_decimal`),
  KEY `data_date` (`data_date`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8
PARTITION BY RANGE (key_point_id) (
PARTITION p0 VALUES LESS THAN (163),
PARTITION p1 VALUES LESS THAN (271),
 PARTITION p2 VALUES LESS THAN (364),
 PARTITION p3 VALUES LESS THAN (370), 
 PARTITION p4 VALUES LESS THAN (378),
 PARTITION p5 VALUES LESS THAN (384), 
 PARTITION p6 VALUES LESS THAN (397),
 PARTITION p7 VALUES LESS THAN (460), 
 PARTITION p8 VALUES LESS THAN (487),
 PARTITION p9 VALUES LESS THAN (MAXVALUE));

我正在运行一个简单的选择查询,执行需要很长时间(12 秒)

select data_string,document_id from document_key_points cd where key_point_id =12

解释

+----+-------------+-------+------+---------------+---------+---------+-------+---------+-------------+
| id | select_type | table | type | possible_keys | key      | key_len | ref   | rows    | Extra       |
+----+-------------+-------+------+---------------+----------+---------+-------+---------+-------------+
|  1 | SIMPLE      | cd    | ref  | key_pt_id     | key_pt_id| 4       | const | 1957136 | Using where |
+----+-------------+-------+------+---------------+----------+---------+-------+---------+-------------+

我在这个表中有 5000 万行,目的是优化查询输出接近 1-2 秒, 什么方法可以帮助我优化此查询以达到 1-2 秒?

注意:相同的查询在 8 秒内运行,没有分区。

更新: 添加解释分区

+----+-------------+-------+------------+------+---------------+-----------+---------+-------+---------+-------------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref   | rows    | Extra       |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+---------+-------------+
|  1 | SIMPLE      | cd    | p0         | ref  | key_pt_id     | key_pt_id | 4       | const | 1957136 | Using where |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+---------+-------------+

【问题讨论】:

  • 这是在当前版本的 MySQL 上,比如 5.6 吗?
  • 请解释为什么要对这个表进行分区。除了您尝试优化查询之外,还有什么推动它的吗?您在data_string 列中存储的数据的最大长度是多少?此查询返回多少行?
  • 您的查询似乎正在提取 GiB 左右的数据。在大约十秒钟内要处理很多位。有没有办法在不传输您的 data_string 值的情况下处理此类查询?我之所以这么问,是因为这样的查询通常可以使用复合覆盖索引来优化。另外,分区不太可能有助于该表的查询性能。
  • 您可以通过将key_point_id 索引更改为 (key_point_id, is_deleted) 来稍微改进一下。但我认为最好的办法是调查你需要这百万行来做什么。我不认为你会在一个单一的呐喊中显示它。不管它是什么,它可能被分页吗?如果是这样,可以稍后检索 data_string 吗?等等。
  • 您需要能够在这几秒钟内管理 1GB 的硬件。不是最简单的事情。那个。

标签: mysql optimization query-optimization partitioning


【解决方案1】:

EXPLAIN 表示 SELECT 将返回大约 200 万行。这需要时间,可能主要是 I/O。对于如此大的结果集,您不应该期望亚秒级的响应。

如果您的“真实”查询是其他内容,那么让我们看看。并告诉我们EXPLAIN PARTITIONS SELECT ...,以确认“分区修剪”按预期工作。

您的查询应该如何工作:

  1. 由于 WHERE 子句对 PARTITION 键 (key_point_id =12) 有限制,因此应该进行修剪。
  2. 现在只需要查看分区 p0。该分区有数百万行,对吗?
  3. 接下来使用一些 INDEX 来完成查询;由于 key_point_id=12,优化器选择了 key_point_id。它显然发现大约 2M 行的值为 12,但这只是 p0 的一小部分,值得使用索引。
  4. 所以,我们还没有完成。扫描索引以查找所有 key_point_id=12 条目。这是索引的线性(“范围”)扫描。
  5. 对于每个条目,它必须使用 PRIMARY KEY 访问数据 BTree 以获取 SELECT 要求的字段。这是 InnoDB,所以 PK 的其余部分也在辅助键中。这是基于 (id, key_point_id) 的 2M 探针来查找所需数据。

请注意,如果没有分区(但具有基本相同的索引),步骤 3-5 将解释处理过程。只有琐碎的步骤 1 和 2 会被删除。分区没有给您带来任何性能提升。

但是,您看到了性能差异。您是否运行了两次查询?你在冷系统上运行它吗?我怀疑您看到的差异几乎完全是由于您运行它们时缓存的内容不同。

您拥有的分区仅对以下查询有用(?):

SELECT ... WHERE (key_point_id = ...) AND something else indexed
SELECT ... WHERE (key_point_id BETWEEN..AND..) AND something else indexed

附带说明:KEY data_boolean (data_boolean) 可能从未使用过——索引标志本身不值得。

由于您有更多字段,因此答案会发生变化。

首先,请注意!=NULL错误的

mysql> SELECT NULL != NULL, 'abc' != NULL, NULL IS NOT NULL, 'abc' IS NOT NULL;
+--------------+---------------+------------------+-------------------+
| NULL != NULL | 'abc' != NULL | NULL IS NOT NULL | 'abc' IS NOT NULL |
+--------------+---------------+------------------+-------------------+
|         NULL |          NULL |                0 |                 1 |
+--------------+---------------+------------------+-------------------+

也就是说,在测试时,只有'abc'会被视为不为空。 NULL,本身会测试失败,因此被认为是NULL。

其次,标志和“!=”是任何优化尝试的杀手。它们至少可以变成“=”或“IS NULL”吗?

【讨论】:

  • 原始查询还有两个条件data_string !=null and is_deleted !=1,我已经更新了有问题的解释分区,p0 有 400 万行,我在获取统计信息之前充分预热了缓存,我也在做这个分区的东西只是为了优化查询,我可以采取更好的方法吗?
  • 正如大家所说,获取 2M 行需要时间。如果没有神奇的更快的磁盘和 CPU,就无法对其进行优化。
【解决方案2】:

我创建了同一张表,但没有分区。我已经生成了一些数据。大约 10M 行。根据我的数据,执行您的选择大约需要 25 秒。

如果我将主键更改为 id 并禁用索引 key_point_id 的使用,则此选择将在 8 秒内执行。所以它快了 3 倍。

ALTER TABLE document_key_points DROP primary KEY, ADD primary KEY(id);
SELECT data_string,document_id FROM document_key_points USE INDEX () WHERE key_point_id = 9;

我做了更多的测试。我已经创建了带有分区的表。我在表中有多少条记录并不重要。唯一的问题是 1 个分区中有多少条记录。

因此,如果我在 1 个分区中有 1-2 百万行,我可以在不到 2 秒的时间内提取 100 万行。如果我禁用索引,我可以在 0.8 秒内提取数据。

在 1 个分区中有 3-5 百万条记录,如果我不使用索引,我可以在大约 4 秒内使用索引加载数据,而在 2 秒内加载数据。

我建议创建更多分区并删除索引 key_point_id 因为对我来说它看起来完全没用。在我所有的测试中,没有索引的查询运行速度至少快 2 倍。

【讨论】:

  • 在我的原始表中,我有 5700 万行,我只能使用覆盖索引优化上述查询直到 8.51 秒(接近 100 万行)
  • 我明天会尝试 5000 万。但问题是,如果您需要从表中提取所有数据(在您的情况下为 1\50),那么索引只会减慢它的速度。
  • 我用随机生成的数据做了更多的测试。请参阅我的更新答案。
  • 你的统计数据很有趣,写索引,带走一点:分区后总是重新访问你的索引。感谢更新。
  • 还有另一个维度需要考虑:分区与不分区; INDEX(key_point_id) 与否;以及分区(或表,如果未分区)的百分比包含所需的 key_point_id。如果分区包含该行的很大一部分,它将执行表扫描而不是使用索引。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-18
  • 2016-07-11
  • 1970-01-01
  • 1970-01-01
  • 2018-06-28
  • 1970-01-01
相关资源
最近更新 更多