如何在大表上优化这个mysql连接?

时间:2013-01-03 13:28:33

标签: mysql optimization join query-optimization

我有一个项目,管理员需要创建多个简报,其中包含一些来自网络的抓取帖子。

我在抓取完成后在posts表格中插入帖子,并为其分配feed_id以识别来源。这是posts表(截断)的结构:

CREATE TABLE `posts` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `feed_id` int(11) NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL,
  `identifier` varchar(255) DEFAULT NULL,
  `published` timestamp NULL DEFAULT NULL,
  `content` longtext,
  ...
  ...
  `is_unread` int(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

每个管理员(用户)都可以访问一个或多个“Feed”。因此,在时事通讯创建页面中,我想向他们展示他们可以看到的提要中的帖子列表,并且,我还会显示一个按钮,将帖子放在该时事通讯的特定类别中,如果用户之前选择了该帖子,我应该告诉他,让他从类别中删除它。所以我也有其他一些表:newsletterscategoriesnewsletter_postcategory_post。这是他们的结构:

newsletters

CREATE TABLE `newsletters` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL,
  `sent_at` timestamp NULL DEFAULT NULL,
  `title` varchar(255) DEFAULT NULL,
  `date` date DEFAULT NULL,
  `topic_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

categories

CREATE TABLE `categories` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `topic_id` int(11) NOT NULL,
  `title` varchar(255) DEFAULT NULL,
  `slug` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

newsletter_post

CREATE TABLE `newsletter_post` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL,
  `newsletter_id` int(11) NOT NULL,
  `post_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

category_post

CREATE TABLE `category_post` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL,
  `category_id` int(11) NOT NULL,
  `post_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

所以我正在使用此查询来查找允许的Feed的帖子,并检查帖子是否属于此特定时事通讯的特定类别:

SELECT DISTINCT `posts`.`id`, `published`, `posts`.`title`, `posts`.`content`, `source_name`, `category_id`, `newsletter_id`, `link_href`, categories.title as category_title
FROM `posts`
LEFT JOIN `category_post` ON `posts`.`id` = `category_post`.`post_id`
LEFT JOIN `categories` ON `categories`.`id` = `category_post`.`category_id`
LEFT JOIN `newsletter_post` ON `posts`.`id` = `newsletter_post`.`post_id`
LEFT JOIN `newsletters` ON `newsletters`.`id` = `newsletter_post`.`newsletter_id`
WHERE `feed_id` IN (6, 7) ORDER BY `posts`.`published` DESC LIMIT 40 OFFSET 0

但问题是这很糟糕而且没有优化。我的posts表每月最多包含50,000行,每行包含3~10kbs的平均数据,因此有时当我尝试运行查询时(通常由管理员运行以生成新闻稿,分页等)mysql显示这个错误:加入太多行等等,而且大多数时候它真的很慢。

我在一个查询中执行所有操作的原因是因为我希望结果存在于一个json响应中,因此我可以快速向用户显示,而无需执行其他请求。

我想知道是否有更好的方法来执行此查询或使用索引或其他内容。 提前感谢您的帮助。

4 个答案:

答案 0 :(得分:4)

索引你的帖子表

(feed_id,已发布)

所以数据已针对您的WHERE子句进行了优化,并预先排序以帮助您进行ORDER BY。

答案 1 :(得分:3)

对于阅读需求量很大的查询,InnoDB的效率非常低。我建议你使用NoSQL数据库,但如果你不想要,或者改变成本太高......你可以试试这个:

1)LIKE Sallar Kaboli告诉你,你必须在JOIN查询中使用的列中索引你的表。例如:

      CREATE INDEX index1 ON newsletter_post (post_id);

2)仅使用JOINS的重要列。

我的意思是,你必须只使用查询的SELECT部分​​中使用的列。

我希望这会有所帮助。

答案 2 :(得分:2)

要完成其他答案,我建议在posts表格中更改此类型:

1)将feed_id更改为int(4)。你真的有int(4)个饲料吗? 2)将is_unread更改为bit而不是int(1)。我应该说这可能无法改善您在问题中的给定查询,但根据字段名称,正确的类型为bit

对此答案的另一个改进是,永远不要对数字或id字段使用默认int(11),指定更具体的类型。使用较小尺寸的类型也会改善您的索引。对于字段ID,我认为您不需要int(4)以上。

例如,索引和查询int(3)列比int(11)更快。

答案 3 :(得分:1)

请在::

上创建以下索引索引
1) `post_id` in `category_post`
2) `post_id` in `newsletter_post`