【问题标题】:Partition huge chat messages table in mysql在mysql中对巨大的聊天消息表进行分区
【发布时间】:2020-09-06 17:09:00
【问题描述】:

我在 mysql 中有一个表,其中大约 7000 亿行用于表示聊天消息。文本存储在单独的表格中。

+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| ID         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| Type       | tinyint(4)       | NO   |     | 0       |                |
| FromUserID | int(10) unsigned | NO   | PRI | 0       |                |
| ToUserID   | int(10) unsigned | YES  | MUL | NULL    |                |
| TextID     | int(10) unsigned | YES  | MUL | NULL    |                |
| Ts         | datetime         | YES  |     | NULL    |                |
| IsNew      | tinyint(4)       | NO   |     | 0       |                |
| Direction  | tinyint(4)       | NO   |     | 0       |                |
| NeedStar   | tinyint(4)       | NO   |     | 0       |                |
| NeedSend   | tinyint(4)       | NO   |     | 0       |                |
| DirectID   | int(10) unsigned | YES  | MUL | NULL    |                |
| IdeaID     | int(11)          | YES  |     | NULL    |                |
| FilePos    | int(11)          | NO   |     | 0       |                |
+------------+------------------+------+-----+---------+----------------+

到目前为止,它已被分区BY HASH(FromUserID) PARTITIONS 16; 并索引如下:

--------------------+--------------+-------------+-----------+-------------+
 Key_name           | Seq_in_index | Column_name | Collation | Cardinality |
--------------------+--------------+-------------+-----------+-------------+
 PRIMARY            |            1 | ID          | A         |   644937873 |
 PRIMARY            |            2 | FromUserID  | A         |   644937873 |
 DoulikeMessage_k_2 |            1 | FromUserID  | A         |     5971646 |
 DoulikeMessage_k_2 |            2 | ToUserID    | A         |   644937873 |
 DoulikeMessage_k_3 |            1 | DirectID    | A         |          28 |
 ToUserID           |            1 | ToUserID    | A         |    37937521 |
 TextID             |            1 | TextID      | A         |   644937873 |
--------------------+--------------+-------------+-----------+-------------+

我正在考虑创建一个具有不同分区和索引的类似表,然后在那里传输记录。对表最频繁的查询是

SELECT  DoulikeMessages.* FROM DoulikeMessages WHERE  1  AND (DoulikeMessages.FROMUSERID = '2048254')  AND DoulikeMessages.TOUSERID >= '1'  AND DoulikeMessages.TYPE <= '1'  ORDER BY Ts DESC;

有时需要长达 20 秒的时间来处理。那是因为 Ts (datetime) 上没有索引。我考虑做类似PARTITION BY RANGE( FromUserID ) PARTITIONS 50 的事情。并按 Ts 索引。或者也许我应该按日期制作子分区?我可以在查询中添加诸如“Where TS > now()- 1 Month”之类的内容,因为大多数时候只需要最近的消息。而旧的只是稍后在代码中被丢弃。

另外我应该提到有人尝试手动分区表(基于 fromuserid 创建 50 个新表并更改 api 以使用它们),但这对我来说似乎是个坏主意。我不明白如何从这种方法中受益。

【问题讨论】:

    标签: mysql database-partitioning


    【解决方案1】:

    分区不是让您的查询更快的工具。这就是索引的用途。

    目前访问您的数据效率低下。虽然您在FromUserID 上有一个索引,但要获取实际数据(您想要*),MySQL 必须在表中查找它。但是在那里,数据是(随机)按自动增量 ID 排序的。

    所以 MySQL 首先读取索引的一行,获知完整数据在表中的存储位置,然后跳转到表中的那个位置,读取那里的数据,然后重复索引中的下一行。

    此外,表中的数据以块的形式存储,通常为 16kb。因此,要从具有 41 字节的表中读取一行,MySQL 必须实际读取 16kb。也许从磁盘。这 41 个有用的字节也将使用 16kb 的缓冲池(MySQL 缓存),并且可能会迫使您比需要更早地从磁盘而不是内存中重新读取数据,因为旧数据会更快地被替换。或者换句话说:要将整个表缓存在内存中,您需要 28gb 的内存(假设您在写入 7000 亿行时是指 7 亿行)。如果当前只有 1% 的用户处于活动状态,那么在最坏的情况下,您可能仍然需要 28gb 的内存,因为超过 99% 的缓存数据可能无法使用,因为您只需要 16kb 中的 41 个字节。有一些重叠(例如,大多数块将包含您需要的几行),但您明白了。如果块将包含同一用户的大部分数据,您可能只需要 0.28gb 来缓存 1% 活跃用户的数据,并让其余数据缓存其他数据(或足以将其全部保存在内存中)。

    只是要强调:从磁盘读取是一项非常非常昂贵的操作。只需对随机用户(尚未在内存中)运行两次查询。第一次从磁盘读取。第二次从内存中读取。根据您的 MySQL 版本,您可能需要添加 SQL_NO_CACHE 以获得准确的结果,例如得到缓冲池的实际效果,而不仅仅是查询缓存的效果。

    为了优化您的访问,当您跳转到FromUserID 时,您应该手头有您需要的所有数据。您可以使用覆盖索引,这意味着您可以使用所需的所有列创建索引。如果您需要所有列都覆盖*,这可能意味着索引(FromUserID, Ts, ToUserID, Type, ..., FilePos)。它将占用与当前表数据一样多的磁盘空间,但它将允许 MySQL 从该索引中读取一行,意识到它拥有所有数据,然后从索引中读取下一行。无需查阅表格即可获取完整数据。这要快得多。

    添加 MySQL 一次读取 16kb 的效果,所以如果需要的数据没有缓存,必须从磁盘读取,你已经将接下来的 16kb/41byte=400 行读取到内存中,因此最多可以减少 399 磁盘读。如果这是您看到或没有看到的效果,将取决于您当前拥有多少缓存内存。但是对于一个极其简单的查询来说,20s 表明您没有将整个表放在内存中。

    您也可以考虑将主键更改为以FromUserID 开头的内容,也许是FromUserID, Ts, ToUserID。虽然它为覆盖索引节省了磁盘空间,但这样做的决定更符合逻辑考虑,例如如果这个(或你的列的其他组合)在逻辑上实际上是一个主键(例如唯一而不是空),这取决于你的数据模型。您不应该仅仅为了节省磁盘空间而这样做。如果您这样做,这将与覆盖索引一样,还允许您一次性读取用户的所有数据,而无需查阅其他数据源。

    根据这些查询返回的消息数量、磁盘速度(ssd 或 hdd)、ram/缓冲池的数量和服务器负载,执行时间应该以毫秒而不是秒来衡量。

    【讨论】:

      猜你喜欢
      • 2012-01-11
      • 2020-08-16
      • 1970-01-01
      • 2012-08-25
      • 2021-08-19
      • 1970-01-01
      • 2021-02-27
      • 1970-01-01
      • 2021-03-06
      相关资源
      最近更新 更多