计算用户的飞行年龄,优化。 MySQL的

时间:2013-06-05 08:51:07

标签: mysql optimization

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

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                                               |
+----+-------------+-------+--------+-------------------+---------+---------+--------------------+--------+----------+-----------------------------------------------------------+

3 个答案:

答案 0 :(得分:3)

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

看看你的帖子,至少你提供了一个EXPLAIN计划 - 但表结构也会有所帮助。

为什么查询中的表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

答案 1 :(得分:1)

更改i表,并添加名为TIMESTAMP的{​​{1}}或DATETIME列,其中包含date_of_birth

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);

答案 2 :(得分: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 |
+----+-------------+----------+-------+---------------+---------+---------+------+------+--------------------------+