如何使用GROUP BY(exec_datetime)优化COUNT(DISTINCT ip_address)的性能

时间:2012-11-23 08:31:12

标签: mysql

我有以下查询,显示每天发出请求的不同IP地址。

SELECT COUNT(DISTINCT ip_address) as ip_address, DATE(exec_datetime) as day
FROM requests
GROUP BY MONTH(exec_datetime), DAY(exec_datetime);

EXPLAIN的输出如下

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  requests    ALL NULL    NULL    NULL    NULL    472043  Using filesort

我对覆盖索引没有清楚的理解,因为当我创建一个索引时,查询花了很长时间才完成

ALTER TABLE requests ADD INDEX unique_ip_per_time(ip_address, exec_datetime);

以下是EXPLAIN

的输出
id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  requests    index   NULL    unique_ip_per_time  268 NULL    472043  Using index; Using filesort

如何通过创建索引或重写它来优化此查询?

修改

两个语句的执行时间约为15秒(有和没有覆盖索引)。此表中唯一的其他键是UNIQUE代理和INDEX上的ip_address

show indexes from requests

Table   Non_unique  Key_name    Seq_in_index    Column_name Collation   Cardinality Sub_part    Packed  Null    Index_type  Comment Index_comment
requests    0   PRIMARY 1   request_id  A   386577  NULL    NULL        BTREE       
requests    1   ip_address  1   ip_address  A   193288  NULL    NULL    YES BTREE       
requests    1   unique_ip_per_time  1   ip_address  A   163 NULL    NULL    YES BTREE       
requests    1   unique_ip_per_time  2   exec_datetime   A   163 NULL    NULL    YES BTREE       

编辑2

我按照eisberg的说明操作,但这个查询大约需要1.1秒......

EXPLAIN SELECT
  A.request_day,
  (
    SELECT COUNT(DISTINCT B.ip_address)
    FROM requests B
    WHERE B.exec_date = A.request_day
  ) as num_ip_addr
FROM request_days A
ORDER BY A.request_day ASC;

这比这个需要大约.9秒

的查询稍慢
SELECT COUNT(DISTINCT ip_address) as ip_address, exec_date
FROM requests
GROUP BY exec_date;

我认为我不需要使用日期创建附加表。是否有任何优化可以应用于DISTINCT ip_address的部分语句(这似乎是瓶颈)?

3 个答案:

答案 0 :(得分:1)

我为这类问题创建了一些解决方法。但是你需要做一些工作。

首先,您可以根据要求创建一个额外的列,以避免在选择期间进行额外的计算:

ALTER TABLE requests ADD COLUMN (request_day DATE);

ALTER TABLE requests ADD INDEX i1(request_day);

UPDATE requests SET request_day = DATE(exec_datetime);

您需要一个额外的表来记住您可以/想要选择的日子:

CREATE TABLE request_days (
  request_day DATE
);

ALTER TABLE request_days ADD UNIQUE INDEX i1(request_day);

INSERT IGNORE INTO request_days SELECT DATE(exec_datetime) FROM requests;

最后你可以:

EXPLAIN
SELECT
  A.request_day,
  (
    SELECT COUNT(DISTINCT B.ip_address)
    FROM requests B
    WHERE B.request_day = A.request_day
  )
FROM request_days A
ORDER BY A.request_day DESC

给出了:

ID  SELECT_TYPE         TABLE   TYPE    POSSIBLE_KEYS   KEY KEY_LEN REF                         ROWS    EXTRA
1   PRIMARY             A       index   (null)          i1  4       (null)                      1       Using index
2   DEPENDENT SUBQUERY  B       ref     i1              i1  4       db_2_95a42.A.request_day    1       Using where

我希望这会对你有所帮助!

关于SQL小提琴的示例:http://sqlfiddle.com/#!2/95a42/2

答案 1 :(得分:0)

由于您在exec_datetime上使用DATE功能,引擎将扫描表格的所有行。 您应该尝试partitioning the table on exec_datetime http://dev.mysql.com/doc/refman/5.1/en/partitioning.html

答案 2 :(得分:0)

理想情况下,您只需添加复合功能索引,如下所示:

CREATE INDEX month_day_idx
   ON requests (MONTH(exec_datetime), DAY(exec_datetime));

不幸的是,MySQL不支持功能索引。相反,您有两个选择:

  1. 为月和日创建额外的列,并使用这2个新字段创建复合索引。

  2. 如果可以,可以改变GROUP BY不使用功能。