1

我有一个网站,其中有一个活动源,类似于 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 秒。

4

2 回答 2

2

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在插入记录时按升序分配列值,那么date_created在您的ORDER BY操作中使用 是多余的。但 MySQL 不知道这一点,因此将其排除在外可能会有所帮助。如果您在插入时始终使用当前日期,并且从不更新日期,这将是正确的。

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

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

于 2014-05-06T22:15:52.517 回答
0

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

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

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

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

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

于 2014-05-06T20:30:43.900 回答