【问题标题】:MySQL Left Join, Group By, Order By, Limit = Terrible PerformanceMySQL Left Join、Group By、Order By、Limit = 糟糕的性能
【发布时间】:2010-10-16 09:37:26
【问题描述】:

我目前正在开发一个应用程序,允许用户使用各种参数搜索文档数据库并返回一组分页结果。我在 PHP/MySQL 中构建它,这不是我常用的开发平台,但到目前为止它已经很盛大了。

我遇到的问题是,为了返回完整的结果集,我必须在每个表上使用 LEFT JOIN,这完全破坏了我的性能。开发数据库的人说我正在使用的查询将返回正确的结果,所以这就是我必须使用的。查询如下,我绝不是 SQL Guru,可以在这方面使用一些帮助。

我一直在想,将查询拆分为子查询可能会更好?以下是我当前的查询:

    SELECT d.title, d.deposition_id, d.folio_start, d.folio_end, pl.place_id, p.surname, p.forename, p.person_type_id, pt.person_type_desc, p.age, d.manuscript_number, dt.day, dt.month, dt.year, plc.county_id, c.county_desc
 FROM deposition d 
 LEFT JOIN person AS p ON p.deposition_id = d.deposition_id 
 LEFT JOIN person_type AS pt ON p.person_type_id = pt.person_type_id 
 LEFT JOIN place_link AS pl ON pl.deposition_id = d.deposition_id 
 LEFT JOIN date AS dt ON dt.deposition_id = d.deposition_id 
 LEFT JOIN place AS plc ON pl.place_id = plc.place_id 
 LEFT JOIN county AS c ON plc.county_id = c.county_id
 WHERE 1 AND d.manuscript_number = '840' 
 GROUP BY d.deposition_id ORDER BY d.folio_start ASC
 LIMIT 0, 20

任何帮助或指导将不胜感激!

沉积台:

CREATE TABLE IF NOT EXISTS `deposition` (
  `deposition_id` varchar(11) NOT NULL default '',
  `manuscript_number` int(10) NOT NULL default '0',
  `folio_start` varchar(4) NOT NULL default '0',
  `folio_end` varchar(4) default '0',
  `page` int(4) default NULL,
  `deposition_type_id` int(10) NOT NULL default '0',
  `comments` varchar(255) default '',
  `title` varchar(255) default NULL,
  PRIMARY KEY  (`deposition_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

日期表

CREATE TABLE IF NOT EXISTS `date` (
  `deposition_id` varchar(11) NOT NULL default '',
  `day` int(2) default NULL,
  `month` int(2) default NULL,
  `year` int(4) default NULL,
  PRIMARY KEY  (`deposition_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Person_Type

CREATE TABLE IF NOT EXISTS `person_type` (
  `person_type_id` int(10) NOT NULL auto_increment,
  `person_type_desc` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`person_type_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=59 ;

【问题讨论】:

  • 您的所有用于连接表格的键都有索引吗? (以及在原稿编号上)
  • 有关表架构的任何信息,尤其是索引都会更有帮助。
  • 这里为什么需要GROUP BY
  • 感谢您的快速回复。 @Quassnoi - Group BY 是因为多个结果将以相同的 deposit_id 返回。基本上我们只想列出 1 个 deposit_id,因为这些会被链接到一个转录本。 @dev0-null-dweller - 我现在将发布表结构。
  • 你的数据类型是一团糟尝试为每个字段选择正确的数据类型,即页面 smallint 无符号(2 字节)与有符号整数(4 字节),对开日期为 varchar(4) 所以我猜它们是 2010 年等,因此请使用 smallint unsigned 或适当的日期/时间数据类型。您的主键是 varchar(11) 也许您应该使用代理键 int unsigned (4 bytes) etc etc etc..

标签: php mysql database performance


【解决方案1】:

您似乎想在每次证词中选择一个人、地点等。

你写的查询会返回这个,但不保证会返回哪个,而且查询效率低。

试试这个:

SELECT  d.title, d.deposition_id, d.folio_start, d.folio_end, pl.place_id, p.surname, p.forename, p.person_type_id, pt.person_type_desc, p.age, d.manuscript_number, dt.day, dt.month, dt.year, plc.county_id, c.county_desc
FROM    deposition d
LEFT JOIN
        person p
ON      p.id = 
        (
        SELECT  id
        FROM    person pi
        WHERE   pi.deposition_id = d.deposition_id
        ORDER BY
                pi.deposition_id, pi.id
        LIMIT 1
        )
LEFT JOIN
        place_link AS pl
ON      pl.id = 
        (
        SELECT  id
        FROM    place_link AS pli
        WHERE   pli.deposition_id = d.deposition_id
        ORDER BY
                pli.deposition_id, pi.id
        LIMIT 1
        )
LEFT JOIN
        date AS dt
ON      dt.id = 
        (
        SELECT  id
        FROM    date AS dti
        WHERE   dti.deposition_id = d.deposition_id
        ORDER BY
                dti.deposition_id, pi.id
        LIMIT 1
        )
LEFT JOIN
        place AS plc
ON      plc.place_id = pl.place_id 
LEFT JOIN
        county AS c
ON      c.county_id = plc.county_id
WHERE   d.manuscript_number = '840' 
ORDER BY
        d.manuscript_number, d.folio_start
LIMIT   20

deposition (manuscript_number, folio_start) 上创建一个索引以使其快速工作

同时在personplace_linkdate 上为(deposition_id, id) 创建一个复合索引。

【讨论】:

  • 您将不得不原谅我的无知,但我假设这仍然允许我返回没有与证词等相关的人的结果?
  • @TGuimond:当然。顺便说一句,我更正了查询,其中有一个错误。
  • @Quassnio:我发布了一些其他有问题的表架构,我尝试了你的建议,但不得不稍微更新一下查询,我的开发服务器崩溃了!绝对是我的一个错误,但我认为添加索引已经解决了它。感谢您的帮助!
【解决方案2】:

性能不佳几乎可以肯定是由于缺少索引。您的沉积表没有任何索引,这可能意味着您引用的其他表也没有任何索引。您可以首先向您的沉积表添加索引。从 MySQL shell 或 phpMyAdmin,发出以下查询。

ALTER TABLE 沉积 ADD INDEX(deposition_id, original_number);

如果在添加索引后查询执行得更快,您就知道您走在了正确的轨道上。从那里您可能希望在引用列的其他表上放置索引。例如,对于查询“LEFT JOIN person AS p ON p.deposition_id = d.deposition_id”的这一部分,您可以尝试使用向 person 表添加索引。

ALTER TABLE 人 ADD INDEX(deposition_id);

【讨论】:

  • PRIMARY KEY 作为第一个字段的索引的目的是什么?
  • 这似乎有很大帮助。我已将问题编辑到其他一些有问题的表中,我相当确定改进不是由于缓存,因为我尝试了许多不同的查询并且它们的运行速度无限快。您是否建议我也为其余表添加索引?
  • 这些表看起来没问题,因为主键是查询中引用的列。一般来说,索引会有所不同。我在没有适当索引的表上查询了 2000 万行,这需要 10 分钟。使用适当的索引......在 3 秒内。 :)
【解决方案3】:

如果连接的表可能没有匹配的值,您只需要LEFT JOINperson 在您的数据库模式中是否有可能没有匹配的person_type?或者depositiondate 中没有匹配的行? place 没有匹配的 county

对于为使结果有意义而必须存在的任何关系,您可以将 LEFT JOIN 更改为 INNER JOIN

这些列应该有索引(如果可能的话是唯一的):

person.deposition_id
date.deposition_id
place_link.deposition_id
place_link.place_id

date 表的设计看起来很糟糕;我想不出有一个理由有一个日期表,而不是在deposition 表中放置date(或datetime)类型的列。而date 是一个糟糕的表名称,因为它是一个 SQL 保留字。

【讨论】:

  • 我开始在您上面提到的表上使用内连接,但事实证明,这些表中的每一个都可能没有匹配的沉积值,这就是我最终使用所有左连接的原因。数据库已提供给我,但不幸的是,我对 Schema 无能为力!
  • LEFT JOIN 没有理由比 INNER JOIN 慢得多,因此问题很可能是连接列上缺少索引。如果它们不存在,您可以添加索引吗?我了解使用遗留数据库——我专注于修复/增强损坏的应用程序,我看到各种让我头晕目眩的数据库模式。
  • @gregjor:在MySQL 中,LEFT JOIN 中可为空的表始终在嵌套循环中驱动,所以是的,这是有原因的。但是,操作员提到他需要LEFT JOIN
  • @quassnoi:在 MySQL 中,所有连接都在嵌套循环中驱动,同时考虑了连接的完成方式(参考、范围、扫描)。在此查询中,WHERE 子句仅引用沉积表中的列;除 JOIN 外,没有任何联接表符合任何条件。最大的问题是连接列是否被索引(参考连接)或不(ALL连接)。
  • @gregjor:我的意思是“驱动”而不是“领导”。将首先扫描左表(在外循环中),然后在右表上执行搜索(在内循环中)。使用普通连接,可以交换顺序(由优化器选择)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 2020-05-17
  • 2011-05-17
  • 1970-01-01
相关资源
最近更新 更多