【问题标题】:group by where optimization按位置分组优化
【发布时间】:2016-07-14 08:44:21
【问题描述】:

这样的查询:

select * from employe_info 
where id in 
( 
    select max(id) 
    from employe_info
    where date < '2016-02-01' 
    group by employe_id 
) 
and `level` = 1 
limit 10

employe_info 有一百万行。我想查询最近的employe_info 然后过滤。有什么办法可以优化或者新的表设计吗?

下面是简单的建表语句:

CREATE TABLE `employe` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `address` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `employe_info` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `employe_id` int(10) DEFAULT NULL,
  `level` int(2) DEFAULT NULL,
  `date` date DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_employe` (`employe_id`),
  KEY `date_index` (`date`) USING BTREE,
  CONSTRAINT `fk_employe` FOREIGN KEY (`employe_id`) REFERENCES `employe` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

员工的水平随日期而变化。员工具有多层次的价值观。所以我想在某个日期之前查询员工的最新级别。

INSERT INTO `employe` (`name`, `address`) VALUES ('joe', 'joe address');

INSERT INTO `employe` (`name`, `address`) VALUES ('mads', 'mads address');

INSERT INTO `employe` (`name`, `address`) VALUES ('max', 'max address');


INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('1', '1', '2016-01-01');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('2', '1', '2016-01-02');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('3', '1', '2016-01-03');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('1', '2', '2015-01-01');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('2', '3', '2015-10-02');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('3', '4', '2015-08-03');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('1', '6', '2015-06-01');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('2', '2', '2015-09-02');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('3', '4', '2015-06-03');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('1', '1', '2015-07-01');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('2', '1', '2015-10-02');

INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('3', '1', '2015-11-03');

【问题讨论】:

  • select * from employee_info where id in ( select max(id) from employee_info where date

标签: mysql optimization group-by where


【解决方案1】:

您似乎需要员工信息,但仅适用于具有 1 级条目的最新条目。由于您的查询是嵌套的,因此看起来一个人可能有一个日期为 1 级,但在时间要求内较新的日期为 2 级。此评估是否准确并且会取消该人包含在最终输出中的资格?

您最好使用所有部分的单个复合索引来优化查询,而不是使用 3 个单独的索引。如果您只关心 1 级条目而不管他的日期,那么将其包含在索引中会更有帮助。

也就是说,我对您的查询的第一印象是,我会在 (employe_id, date, id) 上有一个索引,因为您想要每个员工的最大 ID。如果您只关心 1 级条目,我会将索引设置为 (level, employee_id, date, id)

能否请您澄清“级别”的上下文?它是否曾经上升/下降或仅上升或下降 1 是顶层而所有其他都是较低级别?它是从上升/下降/上升还是一直接近 1?

【讨论】:

  • 等级是员工的评价,可能一个月换一次(不只是1),会从上/下/上。所以员工列表会显示某个日期之前的员工等级(mybe now),可以按日期查询。
【解决方案2】:

首先你的查询是错误的。它至少有 3 个错误:

1) 第一个查询中有两个where

   select * from employe_info 
   where id in ( ..) 
   where level = 1 
   limit 10

2) 你在where之前使用group by

  select max(id) 
  from employe_info 
  group by employe_id where date < '2016-01-01'

3) 在一个查询中,您使用的是limit 10,此查询基于使用max(id) 的查询,它将只返回一行。

接下来,您的查询完全陌生,可以 100% 替换为更高效的查询。

这里是玩具查询的解释:

mysql> explain SELECT
    ->     *
    -> FROM
    ->     employe_info
    -> WHERE
    ->     id IN (SELECT
    ->             MAX(id)
    ->         FROM
    ->             employe_info
    ->         WHERE
    ->             date < '2016-01-02'
    ->         GROUP BY employe_id)
    ->         AND level = 1
    -> LIMIT 10;
+----+--------------------+--------------+-------+---------------+------------+---------+------+------+----------------------------------------------+
| id | select_type        | table        | type  | possible_keys | key        | key_len | ref  | rows | Extra                                        |
+----+--------------------+--------------+-------+---------------+------------+---------+------+------+----------------------------------------------+
|  1 | PRIMARY            | employe_info | ALL   | NULL          | NULL       | NULL    | NULL |    3 | Using where                                  |
|  2 | DEPENDENT SUBQUERY | employe_info | range | date_index    | date_index | 4       | NULL |    1 | Using where; Using temporary; Using filesort |
+----+--------------------+--------------+-------+---------------+------------+---------+------+------+----------------------------------------------+
2 rows in set (0.00 sec)

我们可以看到它进行了两个查询(查询和子查询)。 查询不使用索引,子查询使用index,但也使用temporaryfilesort,这是相当慢的方式。 这是您查询的结果:

数据插入:

INSERT INTO `employe` (`name`, `address`) VALUES ('joe', 'joe address');
INSERT INTO `employe` (`name`, `address`) VALUES ('mads', 'mads address');
INSERT INTO `employe` (`name`, `address`) VALUES ('max', 'max address');
INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('1', '1', '2016-01-01');
INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('2', '1', '2016-01-02');
INSERT INTO `employe_info` (`employe_id`, `level`, `date`) VALUES ('3', '1', '2016-01-03');

这是您查询的结果:

mysql> SELECT
    ->     *
    -> FROM
    ->     employe_info
    -> WHERE
    ->     id IN (SELECT
    ->             MAX(id)
    ->         FROM
    ->             employe_info
    ->         WHERE
    ->             date < '2016-01-02'
    ->         GROUP BY employe_id)
    ->         AND level = 1
    -> LIMIT 10;
+----+------------+-------+------------+
| id | employe_id | level | date       |
+----+------------+-------+------------+
|  1 |          1 |     1 | 2016-01-01 |
+----+------------+-------+------------+
1 row in set (0.01 sec)

我的建议是使用我更简单的查询:

SELECT 
    *
FROM
    employe_info
WHERE
    `date` < '2016-01-02'
ORDER BY `date` DESC
LIMIT 1

你可以看到它会返回和你一样的结果:

mysql> SELECT
    ->     *
    -> FROM
    ->     employe_info
    -> WHERE
    ->     `date` < '2016-01-02'
    -> ORDER BY `date` DESC
    -> LIMIT 1;
+----+------------+-------+------------+
| id | employe_id | level | date       |
+----+------------+-------+------------+
|  1 |          1 |     1 | 2016-01-01 |
+----+------------+-------+------------+
1 row in set (0.00 sec)

让我们检查explain 的新查询

mysql> explain SELECT
    ->     *
    -> FROM
    ->     employe_info
    -> WHERE
    ->     `date` < '2016-01-02'
    -> ORDER BY `date` DESC
    -> LIMIT 1;
+----+-------------+--------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table        | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+----+-------------+--------------+-------+---------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | employe_info | range | date_index    | date_index | 4       | NULL |    1 | Using where |
+----+-------------+--------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.01 sec)

所以这个查询使用索引,没有文件排序和临时表,所以效率很高。

此外,我建议您在将来达到 5 -10 行数百万之后切换到partitioning,这应该可以正常工作。

您还可以将date 列更改为bigint,如果它的子表适合您,则使用时间戳作为日期。

更新

。我想查询与我给定的日期和级别匹配的员工列表。 不仅仅是最近的雇员。这是匹配的员工名单 日期和级别。然后限制。

因此,您需要对查询再添加一个条件。 整个查询将如下所示:

SELECT 
    *
FROM
    employe_info
WHERE
    `date` < '2016-01-02' AND `level` = 1
ORDER BY `date` DESC
LIMIT 1


mysql>    SELECT
    ->         *
    ->     FROM
    ->         employe_info
    ->     WHERE
    ->         `date` < '2016-01-02' AND `level` = 1
    ->     ORDER BY `date` DESC
    ->     LIMIT 1;
+----+------------+-------+------------+
| id | employe_id | level | date       |
+----+------------+-------+------------+
|  1 |          1 |     1 | 2016-01-01 |
+----+------------+-------+------------+
1 row in set (0.00 sec)

mysql> explain
    -> SELECT
    ->     *
    -> FROM
    ->     employe_info
    -> WHERE
    ->     `date` < '2016-01-02' AND `level` = 1
    -> ORDER BY `date` DESC
    -> LIMIT 1;
+----+-------------+--------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table        | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+----+-------------+--------------+-------+---------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | employe_info | range | date_index    | date_index | 4       | NULL |    1 | Using where |
+----+-------------+--------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.00 sec)

【讨论】:

  • 我给我的表创建语句。
  • 对不起,我在编辑 sql 时出错了。我已经改变了。我添加一些数据,你可以知道我的意思。 level 是对员工的评价,可能每个月改变一次(不仅仅是1),它会从up/down/up开始。所以员工列表会显示某个日期之前的员工等级(mybe now),可以按日期查询。
  • @wanghao 您的新更改几乎没有任何变化
  • 我在employee_info中添加了一些其他数据。你可以试试我的sql。每个员工都有许多信息行。我想查询与我给定的日期和级别匹配的员工列表。不仅仅是最近的雇员。它是与日期和级别匹配的员工列表。然后限制。
【解决方案3】:

试试这个查询:

select * from employe_info where date < '2016-02-01' group by employe_id ORDER BY date DESC LIMIT 10;

【讨论】:

    猜你喜欢
    • 2010-11-29
    • 2018-06-18
    • 2015-10-17
    • 1970-01-01
    • 2023-01-30
    • 2012-08-10
    • 1970-01-01
    • 2015-03-25
    • 1970-01-01
    相关资源
    最近更新 更多