【问题标题】:Flat MySQL table with enum-based filters is unexpectedly slow具有基于枚举的过滤器的平面 MySQL 表出乎意料地慢
【发布时间】:2014-06-23 14:21:08
【问题描述】:

我有一个网站,其中有一个活动提要,类似于 Facebook 等社交网站有一个。它是一个“最新的优先”列表,描述了用户采取的行动。在生产中,该表中有大约 200k 个条目。

由于无论如何都会被问到,我将首先分享完整的表结构:

CREATE TABLE `karmalog` (
  `id` int(11) NOT NULL auto_increment,
  `guid` char(36) default NULL,
  `user_id` int(11) default NULL,
  `user_name` varchar(45) default NULL,
  `user_avat_url` varchar(255) default NULL,
  `user_sec_id` int(11) default NULL,
  `user_sec_name` varchar(45) default NULL,
  `user_sec_avat_url` varchar(255) default NULL,
  `event` enum('EDIT_PROFILE','EDIT_AVATAR','EDIT_EMAIL','EDIT_PASSWORD','FAV_IMG_ADD','FAV_IMG_ADDED','FAV_IMG_REMOVE','FAV_IMG_REMOVED','FOLLOW','FOLLOWED','UNFOLLOW','UNFOLLOWED','COM_POSTED','COM_POST','COM_VOTE','COM_VOTED','IMG_VOTED','IMG_UPLOAD','LIST_CREATE','LIST_DELETE','LIST_ADMINDELETE','LIST_VOTE','LIST_VOTED','IMG_UPD','IMG_RESTORE','IMG_UPD_LIC','IMG_UPD_MOD','IMG_GEO','IMG_UPD_MODERATED','IMG_VOTE','IMG_VOTED','TAG_FAV_ADD','CLASS_DOWN','CLASS_UP','IMG_DELETE','IMG_ADMINDELETE','IMG_ADMINDELETEFAV','SET_PASSWORD','IMG_RESTORED','IMG_VIEW','FORUM_CREATE','FORUM_DELETE','FORUM_ADMINDELETE','FORUM_REPLY','FORUM_DELETEREPLY','FORUM_ADMINDELETEREPLY','FORUM_SUBSCRIBE','FORUM_UNSUBSCRIBE','TAG_INFO_EDITED','IMG_ADDSPECIE','IMG_REMOVESPECIE','SPECIE_ADDVIDEO','SPECIE_REMOVEVIDEO','EARN_MEDAL','JOIN') NOT NULL,
  `event_type` enum('follow','tag','image','class','list','forum','specie','medal','user') NOT NULL,
  `active` bit(1) NOT NULL,
  `delete` bit(1) NOT NULL default '\0',
  `object_id` int(11) default NULL,
  `object_cache` text,
  `object_sec_id` int(11) default NULL,
  `object_sec_cache` text,
  `karma_delta` int(11) NOT NULL,
  `gold_delta` int(11) NOT NULL,
  `newkarma` int(11) NOT NULL,
  `newgold` int(11) NOT NULL,
  `migrated` int(11) NOT NULL default '0',
  `date_created` timestamp NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (`id`),
  KEY `user_id` (`user_id`),
  KEY `user_sec_id` (`user_sec_id`),
  KEY `image_id` (`object_id`),
  KEY `date_event` (`date_created`,`event`),
  KEY `event` (`event`),
  KEY `date_created` (`date_created`),
  CONSTRAINT `karmalog_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL,
  CONSTRAINT `karmalog_ibfk_2` FOREIGN KEY (`user_sec_id`) REFERENCES `user` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

在优化此表之前,我的查询有 5 个连接,我遇到了很慢的查询时间。我已经对所有这些数据进行了非规范化,因此不再存在单个连接。所以表和查询是平的。

正如您在表格设计中看到的,有一个“事件”字段,它是一个枚举,包含几十个可能的值。在整个站点中,我根据特定事件类型显示活动提要。通常,该查询如下所示:

SELECT * FROM karmalog as k
WHERE k.event IN ($events) AND k.delete=0 
ORDER BY k.date_created DESC, k.id DESC 
LIMIT 0,30

此查询的作用是在总集中查找与 $events 中传递的任何事件匹配的最新 30 个条目,该事件可以是多个。

由于删除了连接并在大多数字段上都有索引,我希望这会表现得很好,但事实并非如此。在 200k 条目上,它仍然需要 3 秒多,我不明白为什么。

关于解决方案,我知道我可以归档旧条目或按事件类型对表进行分区,但这会对代码产生相当大的影响,我首先想了解为什么上述内容如此缓慢。

作为临时解决方法,我现在正在这样做:

SELECT * FROM
(SELECT * FROM karmalog ORDER BY date_created DESC, id DESC LIMIT 0,1000) as karma
    WHERE karma.event IN ($events) AND karma.delete=0
LIMIT $page,$pagesize

这样做的目的是将基集限制为仅搜索最新的 1000 个条目,希望并猜测我传递的过滤器有 30 个条目。虽然它不是很健壮。它不适用于更罕见的事件,并且会带来分页问题。

因此,我首先想了解为什么我的初始查询速度慢的根本原因,这与我的预期相反。

编辑:我被要求分享执行计划。这是测试查询:

EXPLAIN SELECT * FROM karmalog 
WHERE event IN ('FAV_IMG_ADD','FOLLOW','COM_POST','IMG_VOTE','LIST_VOTE','JOIN','CLASS_UP','LIST_CREATE','FORUM_REPLY','FORUM_CREATE','FORUM_SUBSCRIBE','IMG_GEO','IMG_ADDSPECIE','SPECIE_ADDVIDEO','EARN_MEDAL') AND karmalog.delete=0
ORDER BY date_created DESC, id DESC
LIMIT 0,36  

执行计划:

id            = 1
select_type   = SIMPLE
table         = karmalog
type          = range
possible_keys = event
key           = event
key_len       = 1
red           = NULL
rows          = 80519
Extra         = Using where; Using filesort

我不确定如何阅读上面的内容,但我知道 sort 子句似乎真的会杀死这个查询。使用这种排序,需要 4.3 秒,没有 0.03 秒。

【问题讨论】:

  • 发布 EXPLAIN 的输出以获取所需查询
  • 1 - 您使用的 MySQL 版本是什么? 2 - 提供选择查询的解释
  • 你检查过实际的执行计划吗? “in”子句可能导致表扫描。您可能还需要考虑一种策略,其中您有一个带有 {id, event, date_created) 的表,您的主要搜索约束在其中运行,并将这些结果(在 id 上)与您的 karmalog 表连接。
  • 另外,我看到你有k.delete=0 条件,但delete 上没有索引。但更好的是让索引覆盖这两列 - eventdelete
  • @EugenRieck 完成,编辑问题。 andy:我创建了你建议的复合索引,目前没有性能提升。

标签: mysql database-performance query-performance denormalization covering-index


【解决方案1】:

SELECT * 有时会大大降低有序查询的速度,因此让我们从重构您的查询开始,如下所示:

 SELECT k.* 
   FROM karmalog AS k
   JOIN (
      SELECT id 
        FROM karmalog
       WHERE event IN ($events)
         AND delete=0
       ORDER BY date_created DESC, id DESC
       LIMIT 0,30
        ) AS m ON k.id = m.id
  ORDER BY k.date_created DESC, k.id DESC

这将执行您的ORDER BY ... LIMIT 操作,而无需在排序阶段拖拉整个表格。最后,它将从原始表中查找适当的 30 行并再次对这些行进行排序。这可能会节省大量 I/O 和内存数据混洗。

其次,如果id 列值在插入记录时按升序分配,那么在ORDER BY 操作中使用date_created 是多余的。但 MySQL 不知道这一点,因此将其排除在外可能会有所帮助。如果您在插入时始终使用当前日期,并且从不更新日期,则会出现这种情况。

第三,您可以使用复合覆盖索引进行选择(内部)查询。这是一个包含您需要的所有字段的索引。当你使用覆盖索引时,整个查询可以从索引中得到满足,不需要反弹回原表。这样可以节省磁盘访问时间。

试试这个复合覆盖索引:(delete, event, id)。如果您决定在订购时无法摆脱 date_created 的使用,请尝试以下操作:(delete, event, date_created, id)

【讨论】:

  • 感谢您的精彩回答,我今晚下班时会试试这个
  • 我尝试了您的建议。您开始的文字查询没有太大区别(3 秒),但是通过删除日期排序,查询始终下降到 0.19 秒。我曾经有过双重排序的原因,但我不记得了,所以我暂时保留它。尽管我很贪婪,但我希望通过创建索引来进一步加快速度。奇怪的是,您建议的复合索引 增加 查询时间(0.42 秒)。再次移除它会使时间再次回到 0.19 秒。奇怪的?无论哪种方式,修改后的查询现在都快速而强大,因此被接受,谢谢!
【解决方案2】:

在两个相关问题上添加复合索引。在您的表格中,您可以通过指定例如来做到这一点

KEY `date_created` (`date_created`, `event`)

此键仍可用于满足普通的旧 date_created 范围搜索。但除此之外,event 数据也包含在内,因此 DBS 将能够通过仅查看索引来检测相关行。

如果您愿意,您也可以尝试其他顺序:第一个事件,然后是日期。如果有许多事件类型但您的过滤器只包含很少的事件类型,这可能会进行一些优化。另一方面,我不确定系统是否能够在这种情况下使用LIMIT 子句,所以我不确定这个其他命令是否有任何帮助。

编辑:我完全错过了您的 date_event 索引已经包含此信息。但是,根据您的执行计划,没有使用那个。看起来优化器弄错了。您可以尝试删除 event 索引,也许还可以删除 date 索引,看看会发生什么。

【讨论】:

  • 谢谢。正如我的表转储所示,我已经有了该索引:KEY date_event (date_created,event),
  • @Ferdy:哦,错过了,抱歉。更新了我的答案。
猜你喜欢
  • 2019-12-28
  • 2012-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-01
  • 2020-10-27
  • 1970-01-01
  • 2017-12-01
相关资源
最近更新 更多