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

Posted

技术标签:

【中文标题】如何在大表上优化这个 mysql 连接?【英文标题】:How to optimize this mysql join on large table? 【发布时间】:2012-12-17 20:56:00 【问题描述】:

我有一个项目,管理员需要创建多个时事通讯,其中包含一些从网络上抓取的帖子。

抓取完成后,我将帖子插入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;

每个管理员(用户)都可以访问一个或多个“提要”。因此,在时事通讯创建页面中,我想向他们展示他们被允许查看的提要中的帖子列表,并且我还显示了一个按钮以将帖子放在该时事通讯的特定类别中,如果用户之前选择了该帖子,我应该给他看,让他从类别中删除。所以我还有一些其他的表: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;

因此,我使用此查询来查找允许提要的帖子,并检查帖子是否属于此特定时事通讯的特定类别的状态:

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 响应中,这样我就可以快速向用户展示而无需执行其他请求。

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

【问题讨论】:

请粘贴解释计划 @SashiKant 这是解释计划:d.pr/i/UKgh(posts 表现在包含 ~2500 行) 【参考方案1】:

请在 :: 上创建以下索引索引

1) `post_id` in `category_post`
2) `post_id` in `newsletter_post`

【讨论】:

我添加了索引,查询比以前快得多:0.1 对 0.9 秒。我是否需要在posts 中为feed_idpublished 添加索引,因为我在WHERE 和ORDER BY 中使用了这些列? @SallarKaboli:看看这些是让数据库引擎扫描所有行的列,它们的索引非常重要,关于你提到的列上的索引,你可以粘贴更新的说明计划,以便我检查是否可以再次优化 是的,你还需要在 feed_id 上添加一个索引,这样会更加优化【参考方案2】:

索引您的帖子表

(feed_id, 已发布)

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

【讨论】:

只是普通的索引,比如上面提到的其他表的 Sashi 索引? 是的,您的表中的索引也应该基于您期望在正常基础上获得结果的标准/顺序...除了那些需要连接到表的 pk/fk 的人。但是,如果您加入 ex:TableA.SomeKey = TableB.ForeignSomeKey AND TableB.OtherField='SomeStatus'... 您的 TableB 将受益于( ForeignSomeKey, OtherField )上的索引加入。 谢谢,还有一个问题,如果我需要根据published 以外的其他内容进行排序怎么办?像 source_name ?因为管理员有选择排序类型 +1,正确。对 WHERE/ORDER BY 列进行索引将提高性能。 @SallarKaboli,然后根据您将使用的标准/顺序提供另一个索引。引擎应该选择最适合请求的索引。【参考方案3】:

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

1) 就像 Sallar Kaboli 告诉您的那样,您必须在 JOIN 查询中使用的列中为您的表建立索引。例如:

      CREATE INDEX index1 ON newsletter_post (post_id);

2) 仅对 JOINS 使用重要的列。

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

我希望这会有所帮助。

【讨论】:

是的,这对您的情况非常有帮助。首先,你必须分析改变和学习的成本。很多人认为 MongoDB 是所有问题的解决方案,但不是。但我认为可能对您的特定情况有所帮助。【参考方案4】:

要完成其他答案,我建议在posts table 上更改此类型:

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

对这个答案的另一个改进是永远不要对数字或 id 字段使用默认的int(11),分配更具体的类型。使用更小的类型也会改善你的索引。我认为字段 id 不需要超过 int(4)

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

【讨论】:

感谢 Afshin,我从未使用过 bit,所以我不知道它存在 :) 这个答案包含一些关于整数数据类型的错误信息。括号内的数字不影响用于存储它的字节数。您应该使用适当的整数类型,例如INTSMALLINTTINYINT 等,如果你想减少用于存储值的字节数。见***.com/a/4055587/1220706

以上是关于如何在大表上优化这个 mysql 连接?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用具有多个 GROUP BY、子查询和 WHERE IN 在大表上的查询来优化查询?

尽可能快地获取我的 MySQL 的第一行(也在大表上)

在 3 个大表上使用内连接优化 SQL 查询

executeUpdate 在大表上返回负值

必须在大表上的 .Skip() 和 .Take() 之前在实体框架 4.1 中调用 .ToList()

在大表上使用 Django-Filter 以及 DataTables2