【问题标题】:Optimizing ORDER BY Query on large table MySQL优化大表 MySQL 上的 ORDER BY 查询
【发布时间】:2009-07-23 02:49:03
【问题描述】:

我有一个正在尝试优化的浏览类别查询。我以使用临时结束;在解释中使用文件排序,并且查询在具有 60,000 行的类别上很慢。如果我删除 Order By 子句,查询运行速度非常快 0.05 秒以执行 60,000 行。使用 Order By 子句,它的速度非常慢,大约 5 秒。 Parts 和 Parts_Category 一样包含大约 500,000 行。

我有一个名为 sort_index 的部件组索引(状态、级别、仓库、更新)

在解释的顶部我有 |全部 |使用临时的;使用文件排序

所有其他索引都显示正常。有人可以告诉我可能是什么问题吗?我没主意了。也许我应该重新排列这个查询,以便获得更好的性能?

查询。

SELECT Parts.*, Image.type, Image.width, Image.height,
(SELECT name FROM Location_State WHERE id = Parts.state_id) AS state, 
(SELECT name FROM Location_Region WHERE id = Parts.region_id) AS region, 
(SELECT start_date FROM Promotion WHERE id = Parts.promotion_id) AS promotion_start_date, 
(SELECT end_date FROM Promotion WHERE id = Parts.promotion_id) AS promotion_end_date 
FROM ( SELECT parts_id FROM Parts_Category WHERE Parts_Category.category_id = '40' 
UNION SELECT parts_id FROM Parts_Category WHERE Parts_Category.main_category_id = '40') cid 
LEFT JOIN Image ON Parts.image_id = Image.id
JOIN Parts ON Parts.id = cid.parts_id AND Parts.status = 'A'
ORDER BY Parts.level DESC, Parts.warehouse DESC, Parts.updated DESC LIMIT 0, 15
表格部件的表格结构 字段类型 Null 默认值 id int(11) 无 auto_increment image_id int(11) 是 0 gallery_id int(11) 是 0 image_count int(3) 是 0 Promotion_id int(11) 是 0 country_id int(11) 是 NULL state_id int(11) 是 NULL region_id int(11) 是 NULL city_id int(11) 是 NULL area_id int(11) 是 NULL 更新日期时间 是 0000-00-00 00:00:00 输入日期时间 是 0000-00-00 00:00:00 续订日期 是 0000-00-00 discount_id varchar(10) 是 NULL 标题 varchar(100) 是 search_title varchar(255) 是 仓库 varchar(50) 是 url varchar(255) 是 display_url varchar(255) 是 friendly_url varchar(100) 是 NULL 说明 varchar(255) 是 关键字 varchar(1000) 是 NULL 附件文件 varchar(255) 是 attachment_caption varchar(255) 是 状态字符(1) 是 级别 tinyint(3) 是 0 全球 tinyint(1) 是 0 random_number int(11) 是 NULL 提醒 tinyint(4) 是 NULL category_search varchar(1000) 是 video_sn -p varchar(1000) 是 importID int(11) 是 0 索引 主要编号 518623 随机数索引 32201 随机数 country_id 索引 1 country_id state_id 索引 8 state_id region_id 索引 5 region_id 更新日期索引 1 更新日期 全球 INDEX 1 全球 友好的 URL 索引 518623 友好的 URL Promotion_id 索引 1 Promotion_id city_id 索引 1 city_id area_id 索引 1 area_id 邮编索引 2790 邮编 进口 ID 索引 518623 进口 ID image_id 索引 10 image_id -------------- index_browse_category 索引 52 等级 地位 仓库 更新 ----------------- 关键词全文 1 描述 关键词 类别搜索 Parts_Category id int(11) 无 auto_increment parts_id int(11) 否 0 category_id int(11) 否 0 main_category_id int(10) 否 0 指数 初级 初级 519330 id category_id 索引 519330 category_id 零件编号 main_category_id 索引 519330 main_category_id 零件编号

【问题讨论】:

  • 约翰,我在下面给出了初步答案。不过,为了充分优化您的查询,请在我的回答中提供要求的项目。
  • 别忘了手册! MySQL 有一个特定页面处理 ORDER BY 优化:dev.mysql.com/doc/refman/5.0/en/order-by-optimization.html
  • @John:检查您的综合索引。它应该按这个顺序在(status, level, warehouse, updated) 上创建(这很重要)。似乎它现在以错误的方式创建(首先是level,然后是status),这似乎就是我的查询缓慢的原因。发布查询结构时,只需运行SHOW CREATE TABLE parts 并发布其输出:它将输出易于复制和粘贴的CREATE TABLE 语句。
  • @John:创建索引,不要依赖工具,我见过变量以错误顺序进入索引的情况。只需运行CREATE INDEX ix_parts_order ON parts (status, level, warehouse, updated)

标签: sql mysql optimization sql-order-by


【解决方案1】:

尝试这样重写您的查询:

SELECT  p.*, i.type, i.width, i.height,
        (SELECT name FROM Location_State WHERE id = p.state_id) AS state, 
        (SELECT name FROM Location_Region WHERE id = p.region_id) AS region, 
        (SELECT start_date FROM Promotion WHERE id = p.promotion_id) AS promotion_start_date, 
        (SELECT end_date FROM Promotion WHERE id = p.promotion_id) AS promotion_end_date 
FROM    parts p
LEFT JOIN
        image i
ON      i.id = p.image_id
WHERE   EXISTS (
        SELECT  NULL
        FROM    Parts_Category pc
        WHERE   pc.category_id = '40'
                AND pc.parts_id = p.id
        UNION ALL
        SELECT  NULL
        FROM    Parts_Category pc
        WHERE   pc.main_category_id = '40'
                AND pc.parts_id = p.id
        )
        AND p.status = 'A'
ORDER BY
        p.status DESC, p.level DESC, p.warehouse DESC, p.updated DESC
LIMIT   15

您需要以下索引才能有效地工作:

parts (status, level, warehouse, updated) -- this one you have
parts_category (category_id, parts_id)
parts_category (main_category_id, parts_id)

更新:

我刚刚创建了这样的表:

DROP TABLE IF EXISTS `test`.`image`;
CREATE TABLE  `test`.`image` (
  `id` int(11) NOT NULL,
  `type` int(11) NOT NULL,
  `width` int(11) NOT NULL,
  `height` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `test`.`location_region`;
CREATE TABLE  `test`.`location_region` (
  `id` int(11) NOT NULL,
  `name` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `test`.`location_state`;
CREATE TABLE  `test`.`location_state` (
  `id` int(11) NOT NULL,
  `name` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `test`.`parts`;
CREATE TABLE  `test`.`parts` (
  `id` int(11) NOT NULL,
  `status` char(1) NOT NULL,
  `level` int(11) NOT NULL,
  `warehouse` int(11) NOT NULL,
  `updated` int(11) NOT NULL,
  `state_id` int(11) NOT NULL,
  `region_id` int(11) NOT NULL,
  `promotion_id` int(11) NOT NULL,
  `image_id` int(11) NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`),
  KEY `status` (`status`,`level`,`warehouse`,`updated`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `test`.`parts_category`;
CREATE TABLE  `test`.`parts_category` (
  `id` int(11) NOT NULL,
  `parts_id` int(11) NOT NULL,
  `category_id` int(11) NOT NULL,
  `main_category_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `ix_pc_cat_parts` (`category_id`,`parts_id`),
  KEY `ix_pc_main_parts` (`main_category_id`,`parts_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `test`.`promotion`;
CREATE TABLE  `test`.`promotion` (
  `id` int(11) NOT NULL,
  `start_date` datetime NOT NULL,
  `end_date` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

并用示例数据填充它们:

INSERT
INTO    parts
SELECT  id,
        CASE WHEN RAND() < 0.1 THEN 'A' ELSE 'B' END,
        RAND() * 100,
        RAND() * 100,
        RAND() * 100,
        RAND() * 50,
        RAND() * 50,
        RAND() * 50,
        RAND() * 50
FROM    t_source
LIMIT 500000;
INSERT
INTO    parts_category
SELECT  id,
        id,
        RAND() * 100,
        RAND() * 100
FROM    t_source
LIMIT 500000;
INSERT
INTO    location_state
SELECT  id, CONCAT('State ', id)
FROM    t_source
LIMIT 1000;
INSERT
INTO    location_region
SELECT  id, CONCAT('Region ', id)
FROM    t_source
LIMIT 1000;
INSERT
INTO    promotion
SELECT  id,
        '2009-07-22' - INTERVAL RAND() * 5 - 20 DAY,
        '2009-07-22' - INTERVAL RAND() * 5 DAY
FROM    t_source
LIMIT 1000;

上面的查询针对30 milliseconds 运行并产生以下计划:

1, 'PRIMARY', 'p', 'ref', 'status', 'status', '3', 'const', 107408, 'Using where'
1, 'PRIMARY', 'i', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.p.image_id', 1, ''
6, 'DEPENDENT SUBQUERY', 'pc', 'ref', 'ix_pc_cat_parts', 'ix_pc_cat_parts', '8', 'const,test.p.id', 1, 'Using index'
7, 'DEPENDENT UNION', 'pc', 'ref', 'ix_pc_main_parts', 'ix_pc_main_parts', '8', 'const,test.p.id', 1, 'Using index'
, 'UNION RESULT', '<union6,7>', 'ALL', '', '', '', '', , ''
5, 'DEPENDENT SUBQUERY', 'Promotion', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.p.promotion_id', 1, ''
4, 'DEPENDENT SUBQUERY', 'Promotion', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.p.promotion_id', 1, ''
3, 'DEPENDENT SUBQUERY', 'Location_Region', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.p.region_id', 1, ''
2, 'DEPENDENT SUBQUERY', 'Location_State', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.p.state_id', 1, ''

如您所见,没有temporary,没有filesort,一切都非常快。

为了帮助你,我只需要看看你的表是如何定义的。

【讨论】:

  • 再次感谢 Quassnoi。似乎在左连接图像 i 上的左连接附近出现语法错误?
  • 能否请您提供准确的表定义,以便我检查语法?
  • @John:同时,现在试试,我确实弄乱了子句顺序:)
  • Parts Table Col 只是 id 而不是 parts_id,所以我将此行更改为 AND pc.parts_id = p.id,现在它可以工作了。更改索引需要一段时间,所以很快就会回来。
  • 解释 |初级 | p |全部 | 520,000 |使用哪里;使用文件排序。然而,查询速度较慢 - 似乎正在扫描所有行 - 在它只扫描等于返回类别的数量之前?
【解决方案2】:

约翰,问题是您的查询是从派生表中选择的。派生表无法从您的索引中受益。尝试按如下方式更新您的查询:

SELECT
  Parts.*,
  Image.type, Image.width, Image.height,
  Location_State.name AS state,
  Location_Region.name AS region,
  Promotion.start_date AS promotion_start_date,
  Promotion.end_date AS promotion_end_date
FROM Parts
LEFT JOIN Image ON Parts.image_id = Image.id
LEFT JOIN Location_State ON Parts.state_id = Location_State.id
LEFT JOIN Location_Region ON Parts.state_id = Location_Region.id
LEFT JOIN Promotion ON Parts.promotion_id = Promotion.id
INNER JOIN Parts_Category ON (Parts_Category.category_id = 40 OR Parts_Category.main_category_id = 40)
WHERE Parts.status = 'A'
GROUP BY Parts.id
ORDER BY Parts.level DESC, Parts.warehouse DESC, Parts.updated DESC LIMIT 0, 15

注意,如果您的 Location_State、Location_Region、Promotion 表不需要 LEFT JOIN,请改用 INNER JOIN。它可能会表现得更好。

为进一步帮助优化此查询,请提供以下信息:

SHOW CREATE TABLE Parts;

如果我提供的重写查询与您的示例相同(应该),那么还提供:

EXPLAIN <my query here>\G

【讨论】:

  • DESC 子句被MySQL 忽略。所有索引值都按升序存储。
  • 看来你是对的。我从未注意到 MySQL 文档的那部分。谢谢夸斯诺伊。
  • 感谢 hobodave,如果我对类别使用 OR,我需要在零件 ID 上添加 Group By 子句,因为零件可能列在多个类别中,所以我们不希望它们在相同的结果中显示多次. Group By 在与 order by 混合时是邪恶的,因此我们使用 UNION 的原因。不联合所有
  • 您对 GROUP BY 的看法是正确的,但是您能否为您的“GROUP BY 是邪恶的”陈述提供任何实质内容?您提供的查询效率低下,并且不太可能比我建议的更好(添加了 GROUP BY)。在您的查询上使用 EXPLAIN 会显示出一团糟的 DEPENDENT SUBQUERY、派生表等。
  • Hobodave,这样我在第二行 * 附近的某个地方出现了 Sytnax 错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-13
  • 2012-06-12
  • 2012-08-31
  • 1970-01-01
  • 1970-01-01
  • 2012-12-31
  • 1970-01-01
相关资源
最近更新 更多