1

我有一个网站有一个活动的饲料,类似于像Facebook这样的社交网站有一个。这是一个“最新的第一个”列表,描述用户采取的行动。在制作中,该表中约有20万个条目。基于枚举的过滤器的平坦的MySQL表格出乎意料地很慢

因为这将是无论如何询问,我会先共享全表结构:

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 

什么这个查询的作用是找出在总集最新的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 

这样做是限制baseset中搜索到只有最新的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 

我不知道如何读入上面的,但我知道,那种条款似乎真的杀了这个查询。通过这种分类,需要4.3秒,而不需要0.03秒。

+1

EXPLAIN的后期输出以获得期望的查询 –

+0

1 - 您正在使用的是什么MySQL版本? 2 - 提供选择查询的解释 –

+0

您是否检查过实际执行计划? “in”子句可能会导致表扫描。您可能还需要考虑一种策略,在该策略中,您的主要搜索约束运行时有{id,event,date_created}一个表,并将您的karmalog表中的结果(id)加入。 – ErstwhileIII

回答

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操作,而不必四处拖整个表的排序阶段。最后,它将从原始表中查找相应的三十行,然后再重新排序。这可能会节省大量的I/O和内存中的数据混洗。

其次,如果id列值按升序分配为记录插入,然后在你的ORDER BY操作使用date_created是多余的。但是MySQL不知道,所以放弃它可能会有所帮助。如果您在插入时始终使用当前日期,那么这将是真实的,并且从不更新日期。

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

试试本化合物覆盖指数:(delete, event, id)。如果您决定在订购时无法摆脱date_created的使用,请改为:(delete, event, date_created, id)

+0

谢谢你的出色答案,我今晚会试试这个,我下班的时候 – Ferdy

+0

我试了一下你的建议。你开始的字面查询并没有太大区别(3秒),但是通过删除日期排序,查询一直下降到0.19s。我曾经有过这种双重排序的理由,但我不记得了,所以我现在就保留这个。尽管贪婪,但我希望通过创建索引来进一步加快速度。奇怪的是,你建议的复合索引*会增加查询时间(0.42s)。再次移除它会使时间重新回到0.19s。奇怪?无论哪种方式,修改后的查询现在快速和强大,如此接受,谢谢! – Ferdy

0

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

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

此键仍然可以用于满足普通老式date_created范围搜索。但除此之外,还包括event数据,因此DBS只能通过查看索引来检测相关行。

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

编辑:我完全错过了你的date_event指数已经有这方面的信息。根据你的执行计划,虽然这个不被使用。看起来优化器错了。您可以尝试删除event索引,也可以删除date索引,然后查看会发生什么情况。

+0

谢谢。正如我的表转储显示,我已经有了该索引︰KEY'date_event'('date_created','event'), – Ferdy

+0

@Ferdy:哦,错过了那个,对不起。更新了我的答案。 – MvG