0

我有下一个(奇怪的)查询

SELECT DISTINCT c.id
FROM z1 INNER JOIN c c ON (z1.id=c.id) 
INNER JOIN i ON (c.member_id=i.member_id)
WHERE DATE_FORMAT(CONCAT(i.birthyear,"-",i.birthmonth,"-",i.birthday),"%Y%m%d000000") BETWEEN '19820605000000' AND '19930604235959' AND c.id NOT IN (658887)
GROUP BY c.id

用户的生日以三个不同的列保存在 db 中。但这里的任务是找出年龄在特定范围内的用户的东西。

最糟糕的是,mysql会计算每个选定记录的年龄并将其与条件进行比较,这并不好:(有没有办法让它更快?

这是计划

+----+-------------+-------+--------+-------------------+---------+---------+--------------------+--------+----------+-----------------------------------------------------------+
| id | select_type | table | type   | possible_keys     | key     | key_len | ref                | rows   | filtered | Extra                                                     |
+----+-------------+-------+--------+-------------------+---------+---------+--------------------+--------+----------+-----------------------------------------------------------+
|  1 | SIMPLE      | z1    | index  | PRIMARY           | PRIMARY | 4       | NULL               | 176659 |   100.00 | Using where; Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | c     | eq_ref | PRIMARY,member_id | PRIMARY | 4       | z1.id          |      1 |   100.00 |                                                           |
|  1 | SIMPLE      | i     | eq_ref | PRIMARY           | PRIMARY | 4       | c.member_id |      1 |   100.00 | Using where                                               |
+----+-------------+-------+--------+-------------------+---------+---------+--------------------+--------+----------+-----------------------------------------------------------+
4

3 回答 3

3

像往常一样,正确的答案是修复您的架构。即数据应该被规范化,在可行的地方使用本机键并使用正确的数据类型。

看看你的帖子,至少你已经提供了一个解释计划——但表格结构也会有所帮助。

为什么查询中的表是 z1?您没有使用它显式过滤,也没有在任何地方使用结果。

你为什么要做一个 DISTINCT 和一个 GROUP BY - 你要求 DBMS 做同样的工作两次。

为什么你用'c'作为'c'的别名?

为什么要使用 NOT IN 来排除单个值?

为什么将日期值作为字符串进行比较?

优化器可能对解决查询的最佳方法感到困惑 - 但您没有提供任何信息来支持这一点 - 年龄规则过滤了多少比例的数据?使用birthday / i 表驱动查询可能会得到更好的结果:

SELECT DISTINCT c.id
FROM c 
INNER JOIN i ON (c.member_id=i.member_id)
WHERE STR_TO_DATE(
       CONCAT(i.birthyear,'-', i.birthmonth,'-',i.birthday)
       ,"%Y-%m-%d")    
BETWEEN 19820605000000 AND 19930604235959 
AND c.id <> 658887
AND i.birthyear BETWEEN 1982 AND 1993
于 2013-06-05T09:05:42.213 回答
1

你让我解释我的意思。不幸的是,这有两个问题。

首先是我认为这不能在一个简单的评论框中充分解释。

第二个是我真的不知道我在说什么,但我会试一试......

考虑以下示例 - 一个简单的实用程序表,其中包含截至 2038 年的日期(当整个 UNIX_TIMESTAMP 无论如何都停止工作时)......

CREATE TABLE calendar (
    dt date NOT NULL DEFAULT '0000-00-00',
    PRIMARY KEY (`dt`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

现在,以下查询在逻辑上是相同的......

SELECT * FROM calendar WHERE UNIX_TIMESTAMP(dt) BETWEEN 1370521405 AND 1370732400;
+------------+
| dt         |
+------------+
| 2013-06-07 |
| 2013-06-08 |
| 2013-06-09 |
+------------+

SELECT * FROM calendar WHERE dt BETWEEN FROM_UNIXTIME(1370521405) AND FROM_UNIXTIME(1370732400);
+------------+
| dt         |
+------------+
| 2013-06-07 |
| 2013-06-08 |
| 2013-06-09 |
+------------+

...并且 MySQL 足够聪明,可以利用 (PK) 索引来解决这两个查询(而不是读取表本身 - yuk)。

但是,虽然第一个需要对整个索引进行全面扫描(好但不是很好),但第二个能够使用一个(或多个)值范围内的键访问表(太棒了)......

EXPLAIN EXTENDED
SELECT * FROM calendar WHERE UNIX_TIMESTAMP(dt) BETWEEN 1370521405 AND 1370732400;
+----+-------------+----------+-------+---------------+---------+---------+------+-------+--------------------------+
| id | select_type | table    | type  | possible_keys | key     | key_len | ref  | rows  | Extra                    |
+----+-------------+----------+-------+---------------+---------+---------+------+-------+--------------------------+
|  1 | SIMPLE      | calendar | index | NULL          | PRIMARY | 3       | NULL | 10957 | Using where; Using index |
+----+-------------+----------+-------+---------------+---------+---------+------+-------+--------------------------+

EXPLAIN EXTENDED
SELECT * FROM calendar WHERE dt BETWEEN FROM_UNIXTIME(1370521405) AND FROM_UNIXTIME(1370732400);
+----+-------------+----------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table    | type  | possible_keys | key     | key_len | ref  | rows | Extra                    |
+----+-------------+----------+-------+---------------+---------+---------+------+------+--------------------------+
|  1 | SIMPLE      | calendar | range | PRIMARY       | PRIMARY | 3       | NULL |    3 | Using where; Using index |
+----+-------------+----------+-------+---------------+---------+---------+------+------+--------------------------+
于 2013-06-06T13:42:05.190 回答
1

更改i表并添加一个TIMESTAMPDATETIME列以date_of_birtha命名INDEX

ALTER TABLE i ADD date_of_birth DATETIME NOT NULL, ADD INDEX date_of_birth;
UPDATE i SET date_of_birth = CONCAT(i.birthyear,"-",i.birthmonth,"-",i.birthday);

并使用这个应该更快的查询:

SELECT 
    c.id
FROM 
    i
INNER JOIN c 
    ON c.member_id=i.member_id
WHERE
    i.date_of_bith BETWEEN '1982-06-05 00:00:00' AND '1993-06-04 23:59:59'
    AND c.id NOT IN (658887)
GROUP BY
    c.id
ORDER BY
    NULL
于 2013-06-05T09:03:09.197 回答