【问题标题】:Optimize table to reduce index size优化表以减小索引大小
【发布时间】:2017-01-01 06:34:54
【问题描述】:

我有这个保存聊天消息的架构。目前我有大约 100k 行,即大约 5.5MB 的数据。索引大小为 6.5MB。当数据大小约为 4MB 时,索引大小约为 3MB,所以它呈指数增长

CREATE TABLE `messages` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `author` int(11) unsigned DEFAULT NULL,
  `time` int(10) unsigned DEFAULT NULL,
  `text` text,
  `dest` int(11) unsigned DEFAULT NULL,
  `type` tinyint(4) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `history` (`author`,`dest`,`id`) USING BTREE,
  KEY `messages_ibfk_1` (`dest`),
  FULLTEXT KEY `msg` (`text`),
  CONSTRAINT `au` FOREIGN KEY (`author`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `messages_ibfk_1` FOREIGN KEY (`dest`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=105895 DEFAULT CHARSET=utf8;

我针对此表运行并尝试对其进行优化的主要查询是何时需要显示分页历史记录以供 2 人聊天

SELECT id, time, text, dest, type, author 
FROM `messages` 
WHERE (
    (author = ? AND dest = ?) OR (author = ? AND dest = ?)
) AND id <= ? ORDER BY id DESC LIMIT ?, 25

历史记录的其他查询是相同的,只是它们具有针对搜索词或日期范围的附加过滤器。

有什么办法可以减少索引大小并保持最佳性能?

【问题讨论】:

  • 为什么您认为索引大小与性能有关?您的查询运行缓慢吗?毕竟,如果你没有索引,那么你会节省很多空间,但是你的查询会慢很多,所以很明显有一个索引是一种空间性能的权衡,并且通过有索引,你'已经表达了以牺牲空间为代价获得性能的愿望。
  • 如果 MySQL 在 btree 中留下一些未填充的空间以预期将来的插入,则您的索引可能大于表本身。
  • 顺便说一下,您可以通过存储“user1”和“user2”而不是“author”和“dest”来减小索引的大小并提高查询性能,按字母顺序排列两个用户,并制作“user1”是第一个用户,“user2”是第二个用户。因此,如果您想查找 Mark 和 Alice 之间的对话,Alice 将始终是“user1”,而 Mark 将始终是“user2”。然后您可以添加另一列来指示“user1”是作者还是收件人。
  • @WillisBlackburn 回应您的第一条评论 - 我同意,但是当我的指数变得大于我的实际数据时,这对我来说似乎有点关闭。 2 - 有什么办法可以证实这一点,目前索引比数据大 1.3MB。 3 - 我看不出列名和索引大小之间有什么相关性,我的用户也被存储为整数,不确定你的意思。
  • 我不认为你的索引比你的数据大。您看到的 5.5MB 可能不包括“文本”列的大小,因为文本列与主表数据分开存储。毕竟,如果您有 100K 条消息,而它们总共只占用 5.5MB,包括 ID、作者、收件人、日期等所有元数据,那么它们必须是非常短的消息。

标签: mysql indexing data-modeling


【解决方案1】:

不用担心索引的增长。这可能是侥幸;当然不是“指数”。

假设主要问题是性能

SELECT id, time, text, dest, type, author 
FROM `messages` 
WHERE (
    (author = ? AND dest = ?) OR (author = ? AND dest = ?)
) AND id <= ? ORDER BY id DESC LIMIT ?, 25

我看到了三种显着帮助的技术:将OR 更改为UNION,在UNION 中处理LIMIT,以及不要使用OFFSET 进行分页。

        ( SELECT id, time, text, dest, type, author 
            FROM `messages` 
            WHERE author = ?  -- one author & dest
              AND dest = ?
              AND id < ?    -- where you "left off"
            ORDER BY id DESC
            LIMIT 25
        ) UNION ALL
        ( SELECT id, time, text, dest, type, author 
            FROM `messages` 
            WHERE author = ?  -- the other author & dest
              AND dest = ?
              AND id < ?     -- same as above
            ORDER BY id DESC
            LIMIT 25
        )
        ORDER BY id DESC
        LIMIT 25;           -- get the desired 25 from the 50 above

Pagination discussion 解释了为什么应该删除 OFFSET。它讨论了其他技术,包括使用 26(在所有三个位置)而不是 25,以便您知道这是否是“最后一个”页面。

在第一次迭代中,AND id &lt; ? 可以被忽略。或者(更简单),您可以替换一个非常大的数字。

您的索引 (author,dest,id) 最适合我的公式。

随着messages 变大和/或用户在列表中的页面更远,这个复杂的公式将大放异彩。

【讨论】:

  • 这个公式最多涉及 50 行(加上将 50 写入 tmp 并重新读取 50),无论表大小或页码如何。最初的公式总是触及大部分或所有行。
  • 谢谢你的回答,它很有帮助——尤其是关于避免使用偏移进行分页的部分,我过去遇到过这个问题。然而,问题是这个索引大小是否可以接受以及如何减小它,因为 IMH(noob)O 的索引大于我的数据似乎失控 - 这就是为什么我不会将答案标记为已接受。
  • 我还认为值得指出的一点是——从问题中运行原始查询,在 1 毫秒内没有偏移返回,但是在 50 毫秒内得到与联合返回相同的结果。我记得我在设计桌子时也测试过这个,当时也是这样 - OR 多次胜过 UNION
  • 1ms 听起来像是 Query 缓存启动了。尝试使用 SELECT SQL_NO_CACHE ... 来避免 QC。
  • 从未见过失控的索引。随着表格的增长,我已经看到了奇怪的模式。此外,FULLTEXT 的增长方式与普通的 INDEXes 有所不同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-09
  • 2017-03-21
  • 1970-01-01
  • 2017-02-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多