【问题标题】:MySQL Query Optimization (Takes way too long)MySQL 查询优化(花费的时间太长)
【发布时间】:2013-02-21 07:06:36
【问题描述】:

我使用以下准备好的语句:

    SELECT * 
    FROM 
        c_members,c_users,c_positions,c_done_meetings
    WHERE 
        c_positions.POS_ID=c_users.POS_ID 
        AND c_members.CLUB_ID = ? 
        AND USER_POINTS >= ?
        AND USER_POINTS <= ? 
        AND c_users.POS_ID LIKE ? 
        AND MEM_ACADEMY LIKE ? 
        AND MEM_SEX LIKE ? 
        AND MEM_GRADELVL LIKE ? 
        AND MEM_GPA >= ? 
        AND MEM_GPA <= ? 
        AND MEM_ARCHIVE = 0 
GROUP BY 
    c_members.MEM_ID, c_members.CLUB_ID 
HAVING 
    SUM(c_done_meetings.MEDONE_ATTEND = 'u') >= 1 
ORDER BY 
    USER_POINTS DESC

但是,此查询需要 21.971405982971 秒才能加载 111 条记录。当我删除“Having SUM(...)”子句时,性能提高了 100%。有没有办法更好地优化它?

编辑:(表结构)

    CREATE TABLE IF NOT EXISTS `c_done_meetings` (
  `MEM_ID` int(11) NOT NULL,
  `CLUB_ID` int(11) NOT NULL,
  `MEETING_ID` int(11) NOT NULL,
  `MEDONE_ATTEND` varchar(1) NOT NULL COMMENT 'E=excused, U=unexcused, P=present',
  UNIQUE KEY `unique` (`MEM_ID`,`CLUB_ID`,`MEETING_ID`),
  KEY `MEETING_ID` (`MEETING_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

    CREATE TABLE IF NOT EXISTS `c_members` (
  `MEM_ID` int(11) NOT NULL,
  `CLUB_ID` int(11) NOT NULL,
  `MEM_FIRST` varchar(50) NOT NULL,
  `MEM_MIDDLE` varchar(50) DEFAULT NULL,
  `MEM_LAST` varchar(50) NOT NULL,
  `MEM_SEX` tinyint(1) NOT NULL COMMENT '0-Male 1-Female',
  `MEM_EMAIL` varchar(100) DEFAULT NULL,
  `MEM_GRADELVL` int(11) NOT NULL,
  `MEM_ACADEMY` varchar(50) DEFAULT '',
  `MEM_GPA` double DEFAULT '0',
  `MEM_ADDRESS` varchar(500) DEFAULT NULL,
  `MEM_CITY` varchar(100) DEFAULT NULL,
  `MEM_STATE` varchar(100) DEFAULT NULL,
  `MEM_ZIP` int(11) DEFAULT NULL,
  `MEM_TELEPHONE` varchar(25) DEFAULT NULL,
  `MEM_AP` tinyint(1) NOT NULL,
  `MEM_HONORS` tinyint(1) NOT NULL,
  `MEM_ESOL` tinyint(1) NOT NULL,
  `MEM_HISP` tinyint(1) NOT NULL,
  `MEM_WHITE` tinyint(1) NOT NULL,
  `MEM_MULTI` tinyint(1) NOT NULL,
  `MEM_NATIVE` tinyint(1) NOT NULL,
  `MEM_BLACK` tinyint(1) NOT NULL,
  `MEM_ASIAN` tinyint(1) NOT NULL,
  `MEM_EXTRA` varchar(10000) DEFAULT NULL,
  `MEM_ARCHIVE` tinyint(1) NOT NULL DEFAULT '0',
  UNIQUE KEY `MEM_ID` (`MEM_ID`,`CLUB_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

    CREATE TABLE IF NOT EXISTS `c_positions` (
  `POS_ID` int(11) NOT NULL AUTO_INCREMENT,
  `CLUB_ID` int(11) NOT NULL,
  `POS_NAME` varchar(20) NOT NULL,
  `POS_DESC` varchar(500) NOT NULL,
  `POS_ADMIN` tinyint(1) NOT NULL,
  `POS_ATN_VIEW` tinyint(1) NOT NULL,
  `POS_ATN_CHKIN` tinyint(1) NOT NULL,
  `POS_ATN_FINALIZE` tinyint(1) NOT NULL,
  `POS_MEM_VIEW` tinyint(1) NOT NULL,
  `POS_MEM_ADD` tinyint(1) NOT NULL,
  `POS_MEM_EDIT` tinyint(1) NOT NULL,
  `POS_POS_VIEW` tinyint(1) NOT NULL,
  `POS_POS_ADD` tinyint(1) NOT NULL,
  `POS_POS_EDIT` tinyint(1) NOT NULL,
  `POS_MEET_VIEW` tinyint(1) NOT NULL,
  `POS_MEET_ADD` tinyint(1) NOT NULL,
  `POS_MEET_EDIT` tinyint(1) NOT NULL,
  `POS_EVENT_VIEW` tinyint(1) NOT NULL,
  `POS_EVENT_ADD` tinyint(1) NOT NULL,
  `POS_EVENT_EDIT` tinyint(1) NOT NULL,
  `POS_EVENT_UPDATE` tinyint(1) NOT NULL,
  `POS_REPORT_VIEW` tinyint(1) NOT NULL,
  `POS_ARCHIVE_VIEW` tinyint(1) NOT NULL,
  `POS_ANNOUNCEMENTS` tinyint(1) NOT NULL,
  `POS_WEB_CUSTOM` tinyint(1) NOT NULL,
  PRIMARY KEY (`POS_ID`),
  UNIQUE KEY `UNIQUE_NAME` (`CLUB_ID`,`POS_NAME`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=14 ;

    CREATE TABLE IF NOT EXISTS `c_users` (
  `MEM_ID` int(11) NOT NULL,
  `CLUB_ID` int(11) NOT NULL,
  `USER_PIN` int(11) NOT NULL,
  `USER_POINTS` double NOT NULL,
  `POS_ID` int(11) NOT NULL,
  `USER_ARCHIVE` tinyint(1) NOT NULL DEFAULT '0',
  UNIQUE KEY `MEM_ID` (`MEM_ID`,`CLUB_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

编辑 2: 是的,所有 id 都被编入索引,SUM(...) >= # 计算错过的会议次数。 # 是用户设置的参数(我只是硬编码了一个 1 用于测试)

【问题讨论】:

  • 表结构是什么样的?你在使用索引吗?
  • 您是否对查询进行了解释?
  • 应该被索引:c_positions.POS_ID, c_members.CLUB_ID, USER_POINTS, c_users.POS_ID, MEM_ACADEMY ...
  • 对于GROUP BY .. HAVING,MySQL 必须计算所有条目的总和,而不仅仅是通过WHERE 中的条件的条目。现在我看不到在不改变语义(以及结果)的情况下改变它的方法
  • HAVING 在这里可能是邪恶的东西,如果你删除有但添加 AND c_done_meetings.MEDONE_ATTEND = 'u' 到 WHERE 会发生什么?

标签: php sql performance optimization


【解决方案1】:

您需要为 WHERE 子句中使用的所有字段、所有表连接的字段(您需要明确说明您的连接条件,如现在,您正在获得笛卡尔连接)、所有字段用于分组和所有用于排序的字段。

最后一个问题是那里的HAVING 子句。您根本无法使用索引,因为它是一个计算值。如果这是您将在系统中经常使用的查询(即不仅仅是用于报告),您可以考虑添加一个可以用作此过滤目的的标志的字段。每当您在任何查询中设置 c_done_meetings.MEDONE_ATTEND = 'u' 时,您还可以为成员或用户或与之关联的任何内容设置此标志,以便在 WHERE 子句中轻松过滤字段。

除此之外,通过在子选择中获取具有u 值的用户或成员的精简列表,然后使用该子选择作为表进行连接,您实际上可能会获得更好的性能。

编辑:

在看到你的实际表结构后,我可以清楚地看到你需要在哪里添加索引。我也想知道为什么你的表c_usersc_members 具有相同的主键。为什么这些不只是一个表?

【讨论】:

  • 是的,我想我会继续这样做。我不会计算总和,而是将其作为列添加到用户表中。
【解决方案2】:

我突然想到的一些事情:

你经常使用。尝试用你的 php 代码做一些事情,这样就没有必要了。例如,mem_sex 的选择数量有限。将前端设置为单选按钮或下拉菜单,以便发送一个值,您可以在其中使用 = 而不是 like。

二,如果你在 select 子句和适当的 group by 子句中添加 sum(),它应该运行得更快。值得一试。

【讨论】:

  • 我之所以用like是因为我想有空置它的选项,我认为SQL会比PHP快,我会继续使用PHP来构造查询。跨度>
  • 如果你的 sql 说,性别喜欢哪里?,它是空白的,你希望你的查询返回什么?另外,我不是说用php写sql,我是说用php来分配更好的查询参数。
【解决方案3】:

您可以对表进行非规范化并将字段添加到代表您的 c_members 总和(c_done_meetings.MEDONE_ATTEND = 'u')>= 1 但是您需要在更新 c_done_meetings 时始终更新该字段(可以通过触发器完成)

还要尽量避免 LIKE 条件。使用 = insead(至少 SEX 是可能的)

【讨论】:

  • 就像我在另一条评论中所说,我使用 LIKE 因为有时它是空白的。不过我可能会继续做一些 PHP 查询。
  • PHP 中为空时排除条件总是比 SQL 快。因此,如果您排除所有 LIKE 条件并预填充错过会议次数的值,您的查询最多需要几秒钟。
猜你喜欢
  • 2018-01-08
  • 2012-09-03
  • 2019-12-28
  • 1970-01-01
  • 1970-01-01
  • 2022-06-22
  • 2021-05-08
  • 1970-01-01
相关资源
最近更新 更多