【问题标题】:Optimizing my mysql query to use index for sorting优化我的 mysql 查询以使用索引进行排序
【发布时间】:2026-01-24 22:25:02
【问题描述】:

我需要您的帮助来优化下面的查询。让我们假设我们有一个用于文章的 Web 应用程序。软件使用两张表,一张是文章表,一张是用户表。文章表保存文章的创建日期、id、正文、标题和部分。让我们假设我们有一个名为“新闻”的版块,并且有 100 万篇文章属于新闻版块。那么在这种情况下,如何优化以下查询:

SELECT username,title FROM article,users 
WHERE article.auther_id=users.id AND section LIKE 'news' 
ORDER BY article.date DESC 
LIMIT 0,40

表结构为:

CREATE TABLE `article` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`title` VARCHAR( 100 ) NOT NULL ,
`body` VARCHAR( 200 ) NOT NULL ,
`date` VARCHAR( 30 ) NOT NULL ,
`auther_id` INT NOT NULL ,
`section` VARCHAR( 30 ) NOT NULL
) ENGINE = MYISAM ;


CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR( 30 ) NOT NULL
) ENGINE = MYISAM ;

我尝试创建一个包含部分和日期的索引,但它不是最好的,因为如果我们有 200 万条记录并且其中一百万条属于一个部分,数据库将扫描一百万行。

【问题讨论】:

  • 我猜您没有使用 DATE 数据类型作为日期是有原因的?
  • 不,没有理由。当我设计数据库时,我不知道 DATE 数据类型。

标签: sql mysql optimization query-optimization


【解决方案1】:

你需要在(section, date)上创建一个索引。

不要将auther_id作为前导列:文章将在连接中处于前导,并且不会在此列上执行搜索。

由于您的查询中有LIMIT 0, 40MySQL 不必扫描整个索引。它只会选择第一个 40 记录。

这是一个要检查的测试脚本:

CREATE TABLE `article` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`title` VARCHAR( 100 ) NOT NULL ,
`body` VARCHAR( 200 ) NOT NULL ,
`date` VARCHAR( 30 ) NOT NULL ,
`auther_id` INT NOT NULL ,
`section` VARCHAR( 30 ) NOT NULL
) ENGINE = MYISAM ;


CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR( 30 ) NOT NULL
) ENGINE = MYISAM ;

INSERT
INTO    article
SELECT  id,
        CONCAT('Title ', id),
        CONCAT('Body ', id),
        DATE_FORMAT('2009-12-18' - INTERVAL id MINUTE, '%Y-%m-%d %H:%i:%S'),
        (id - 1) % 500 + 1,
        'news'
FROM    t_source;

INSERT
INTO    users
SELECT  id, CONCAT('Username ', id)
FROM    t_source
LIMIT 500;

CREATE INDEX ix_article_section_date ON article (section, date);

SELECT  username,title
FROM    article
JOIN    users
ON      users.id = article.auther_id
WHERE   section = 'news'
ORDER BY
        article.date DESC
LIMIT 0, 40;

t_source 是一个虚拟表,其中包含 1,000,000 行。

最终查询在我的机器上以0.0018 s 完成(立即)

这是执行计划:

1, 'SIMPLE', 'article', 'range', 'ix_article_section_date', 'ix_article_section_date', '92', '', 999998, 'Using where'
1, 'SIMPLE', 'users', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.article.auther_id', 1, ''

【讨论】:

  • 非常感谢。非常丰富的答案,特别是这部分:“由于您的查询中有 LIMIT 0, 40,MySQL 不必扫描整个索引。它只会选择前 40 条记录。”非常感谢你们所有的成员。感谢您的回答。
【解决方案2】:

在 article.auther_id 上添加索引。除了您已经添加的日期和部分的索引。

【讨论】:

    【解决方案3】:

    从查询来看,您似乎需要一个关于部分的索引和一个关于日期的索引(两个单独的索引)。尝试添加这些,然后查看解释计划,看看它们是否有效,或者是否有其他一些调整可能会有所帮助。

    【讨论】:

    • 我很想知道“section LIKE 'news'”和“section = 'news'”是否也有区别。可能不是,但我会检查一下。我通常避免使用 LIKE,除非我需要通配符。
    • @Arthur:我认为这可能会有很大的不同。好点子。
    • 删除 LIKE 会大大加快速度。
    • MySQL 中,LIKE= 对于不带通配符的字符串常量没有区别:dev.mysql.com/doc/refman/5.0/en/range-access-single-part.html
    【解决方案4】:

    好的,首先使用DATE 数据类型作为日期,因为这比使用VARCHAR 快很多。

    其次,您只需将以下键添加到文章表的创建表语法中。

    KEY auther_id (auther_id),
    KEY section (section),
    

    有点无关紧要,但如果您使用 InnoDB,您还可以将 auther_id 作为外键。

    此外,如果您不需要使用“LIKE”,请不要使用 - 只需检查 section="news" 会快很多。 (如果有一个有限列表,您甚至可以使用 ENUM 来表示这些部分。)

    【讨论】:

    • 我将 'LIKE' 替换为 '='。将日期更改为 Mysql 数据需要在软件中进行一些更改。当前数据保存了php函数time()生成的时间戳值。我打算以后改一下。非常感谢。
    【解决方案5】:

    正如其他人所说,日期、部分和 auther_id 的索引。据此:

    http://www.mysqlperformanceblog.com/2008/08/22/multiple-column-index-vs-multiple-indexes/

    拥有一个多列索引而不是三个单独的索引可能会更好,如下所示:

    ...
    key idx_combo (auther_id, section, date)
    ...
    

    在您的表定义中。

    【讨论】:

      【解决方案6】:

      最好的办法是根据日期/部分创建一个分区表。

      然后创建本地分区索引,这样性能会更高。

      【讨论】: