即使对于简单的select语句,选择百万行也很慢

时间:2015-02-17 19:03:57

标签: mysql optimization query-optimization partitioning

我有一个带分区的简单表(数量范围分区为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 |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+---------+-------------+

2 个答案:

答案 0 :(得分:0)

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探测器可以找到所需的数据。
  6. 注意,没有分区(但基本上具有相同的索引),步骤3-5将解释处理。只有简单的步骤1& 2将被删除。 PARTITIONing没有给你带来任何性能提升。

    但是,您看到了性能差异。你运行了两次查询吗?你是在冷系统上运行的吗?我怀疑你看到的差异几乎完全是由于你在运行它们时发生的事情的差异。

    对于这样的查询,您所拥有的分区仅对(?)有用:

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

    旁注:KEY data_booleandata_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,本身将无法通过测试,因此被视为NULL。

    其次,旗帜和"!="任何优化尝试都是杀手。他们至少可以变成" ="或" IS NULL"?

答案 1 :(得分:-1)

我创建了相同的表但没有分区。我已经生成了一些数据。大约1000万行。在我的数据上,执行您的选择大约需要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倍而没有索引。