【问题标题】:MySQL query slow when grouping by PM parent id (vBulletin database)按 PM 父 ID 分组时 MySQL 查询速度慢(vBulletin 数据库)
【发布时间】:2020-06-14 08:50:02
【问题描述】:

我想像 IM 一样从 vBulletin 中获取所有 PM,这应该在我的 .NET Core 库中使用 Dapper。这意味着:A 向 B 发送消息,B 回复将是一个包含两条消息的对话。由于这会导致性能问题,因此我尝试使用 DBeaver 通过直接运行 Dapper 查询来解决问题。

为了从收件箱中获取页面的对话,我编写了以下查询:

SELECT pm.pmid
FROM pm, pmtext AS txt
WHERE pm.pmtextid = txt.pmtextid 
AND (pm.userid = 123 OR txt.fromuserid = 123)
AND pm.folderid != -1
GROUP BY IF(pm.parentpmid != 0, pm.parentpmid, pm.pmid)
LIMIT 0, 50

这为我提供了用户 #123 的前 50 个对话 ID。它有效,但执行时间约为 440 毫秒。我尝试向所有相关字段添加索引

ALTER TABLE pmtext ADD INDEX fromuserid_only(fromuserid);
ALTER TABLE pm ADD INDEX userid_only(userid);
ALTER TABLE pm ADD INDEX parentpmid(parentpmid);

但它仍然很慢。这似乎是由GROUP BY 引起的。即使我只是做GROUP BY pm.parentpmid(这会产生错误的数据,但只是为了性能测试),查询运行时间也不会更好。当我删除 GROUP BY 时,它非常快(~12ms)。

我的计算对话总页数的查询在没有连接的情况下是相似的,而且速度很快(

// DbConnection db = ...
string sqlTotalPages = @"
    SELECT CEIL(COUNT(*)/ 50) AS pages
   FROM pm, pmtext AS txt
    WHERE pm.pmtextid = txt.pmtextid 
    AND (pm.userid = 18 OR txt.fromuserid = 18)";
int totalPages = db.QueryFirstOrDefault<int>(sqlTotalPages);

为什么GROUP BY 会大大降低查询速度?如何提高性能?

vB 中的表结构

CREATE TABLE `pm` (
  `pmid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `pmtextid` int(10) unsigned NOT NULL DEFAULT '0',
  `userid` int(10) unsigned NOT NULL DEFAULT '0',
  `folderid` smallint(6) NOT NULL DEFAULT '0',
  `messageread` smallint(5) unsigned NOT NULL DEFAULT '0',
  `parentpmid` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`pmid`),
  KEY `pmtextid` (`pmtextid`),
  KEY `userid` (`userid`,`folderid`),
  KEY `userid_only` (`userid`),
  KEY `parentpmid` (`parentpmid`)
) ENGINE=MyISAM AUTO_INCREMENT=221965 DEFAULT CHARSET=latin1

CREATE TABLE `pmtext` (
  `pmtextid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `fromuserid` int(10) unsigned NOT NULL DEFAULT '0',
  `fromusername` varchar(100) NOT NULL DEFAULT '',
  `title` varchar(250) NOT NULL DEFAULT '',
  `message` mediumtext,
  `touserarray` mediumtext,
  `iconid` smallint(5) unsigned NOT NULL DEFAULT '0',
  `dateline` int(10) unsigned NOT NULL DEFAULT '0',
  `showsignature` smallint(5) unsigned NOT NULL DEFAULT '0',
  `allowsmilie` smallint(5) unsigned NOT NULL DEFAULT '1',
  `reportthreadid` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`pmtextid`),
  KEY `fromuserid` (`fromuserid`,`dateline`),
  KEY `fromuserid_only` (`fromuserid`),
  KEY `fromuserid_only2` (`fromuserid`)
) ENGINE=MyISAM AUTO_INCREMENT=118470 DEFAULT CHARSET=latin1

【问题讨论】:

  • 速度取决于几个因素。数据存储在一个文件中,因此有几个因素是速度 1)您使用的微型计算机的速度 2)正在使用的磁盘的速度 3)使用密钥等数据的组织 4)数据库的碎片
  • 你考虑过 A) ALTER TABLE pm ADD INDEX pm_parentmid_and_pmid ( parentpmid,pmid);和 B) 删除现有的 parentpmid 以避免出现冗余索引。 C) 将 BOTH != 更改为 > 以获得索引中的正起点?

标签: c# mysql asp.net-core mariadb vbulletin


【解决方案1】:

我认为 GROUP BY 导致处理时间增加的原因是因为 LIMIT。当没有 GROUP BY 时,一旦数据库引擎找到 50 个符合您的条件的行,它就可以停止处理查询中的行。使用 GROUP BY 子句虽然需要处理整个表,将其分组在一起,然后将返回 50 个第一个结果。 至于解决方案,如果您删除 GROUP BY 并将“AND pm.parentpmid = 0”添加到 WHERE 子句中,您会得到正确的结果吗?似乎 GROUP BY 子句可以从结果中删除具有父级的行,使用 WHERE 可以更有效地完成(假设所有具有父级的行在结果中也有父级)。

【讨论】:

    【解决方案2】:

    为了优化您的查询,我需要知道您希望通过 group by 子句实现什么。您能否举一个用您的预期结果填写表格的小例子?

    如果您只想显示父邮件,那么我同意 Erik H 的观点,即最好使用以下查询:

    SELECT pm.pmid
    FROM pm, pmtext AS txt
    WHERE pm.pmtextid = txt.pmtextid 
    AND (pm.userid = 123 OR txt.fromuserid = 123)
    AND pm.folderid != -1
    AND pm.parentpmid = 0
    LIMIT 0, 50;
    

    但这给出的结果与您的查询不同。

    GROUP BY 的效果对我来说看起来很随意。由于 pmid 不是聚合函数的一部分且未分组,因此 MySQL/mariaDB 将返回适用于同一分组的第一个值。

    当我将以下值添加到您的数据库时:

    INSERT INTO pmtext (`fromuserid`, `fromusername`,`title`,`message`,`touserarray`,`iconid`,`dateline`,`showsignature`,`allowsmilie`,`reportthreadid`)
    VALUES 
        (123, 'Pete',  'Titlel',            'Hello1', '', 0, 0, 0, 1, 0),
        (123, 'Pete',  'Title2',            'Hello2', '', 0, 0, 0, 1, 0),
        (2,   'Hank',  'Re: Title1',        'Hello3', '', 0, 0, 0, 1, 0),
        (2,   'Hank',  'Re: Title2',        'Hello4', '', 0, 0, 0, 1, 0),
        (3,   'Chris', 'Re: Title2(a)',     'Hello5', '', 0, 0, 0, 1, 0),
        (2,   'Hank',  'Re: Re: Title2(a)', 'Hello6', '', 0, 0, 0, 1, 0),
        (123, 'Pete',  'Title3',            'Hello7', '', 0, 0, 0, 1, 0),
        (123, 'Pete',  'Re: Re: Title1',    'Hello8', '', 0, 0, 0, 1, 0),
        (123, 'Pete',  'Title4',            'Hello9', '', 0, 0, 0, 1, 0);
    
    INSERT INTO pm ( `pmtextid`, `userid`, `folderid`, `messageread`, `parentpmid`)
    VALUES
      (118470 , 123, 0, 0, 0),
      (118471 , 123, 0, 0, 0), 
      (118472 , 123, 0, 0, 221965),
      (118473 , 123, 0, 0, 221966), 
      (118474 , 123, 0, 0, 221966),
      (118475 , 123, 0, 0, 221969), 
      (118476 , 123, 0, 0, 0),
      (118477 , 123, 0, 0, 221967),
      (118478 , 123, 0, 0, 0);
    

    然后您的查询将返回:

    • 221965, 那是“Title1”
    • 221966,那是“Title2”
    • 221972,那是“Re: Re: Title1”
    • 221970,这是“Re: Re: Title2(a)”
    • 221971, 那是“Title3”
    • 221973, 那是“Title4”

    在我尝试优化您的查询之前,我需要知道这是否是您期望的结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-01
      • 1970-01-01
      • 2014-10-20
      • 2016-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多