34

我想知道什么是更高效和更快的性能:
在一个大表上建立索引或在多个没有索引的较小表上建立索引?

因为这是一个非常抽象的问题,所以让我把它变得更实用:
我有一张包含用户统计信息的表(20,000 个用户和大约 3000 万行)。该表大约有 10 列,包括user_idactionstimestamps等。
最常见的应用是:user_id通过 user_id 插入数据和检索数据(SELECT语句从不包含多个user_id's)。

现在到目前为止,我有一个INDEXuser_id查询看起来像这样

SELECT * FROM statistics WHERE user_id = 1

现在,随着行数越来越多,表格变得越来越慢。INSERT语句变慢了,因为它INDEX变得越来越大;SELECT语句变慢了,好吧,因为要搜索的行更多。

现在我想知道为什么不为每个用户创建一个统计表,而是将查询语法改为这样:

SELECT * FROM statistics_1

其中1代表user_id明显。
这样,不需要INDEX,每个表中的数据要少得多,因此INSERTSELECT语句应该快得多。

现在我的问题又来了:
处理这么多表(在我的情况下是 20,000 个)而不是使用一个表和一个表是否有任何现实世界的缺点INDEX
我的方法是否真的会加快速度,或者查找表格最终会比一切都减慢速度?

4

5 回答 5

91

创建 20,000 个表是个坏主意。不久之后,您将需要 40,000 张桌子,甚至更多。

我在我的书SQL Antipatterns中将这种综合症称为Metadata Tribbles。每次您计划创建“每个 X 的表”或“每个 X 的列”时,您都会看到这种情况。

当您拥有数以万计的表时,这确实会导致真正的性能问题。每个表都需要 MySQL 维护内部数据结构、文件描述符、数据字典等。

还有实际的操作后果。您真的要创建一个系统,每次新用户注册时都需要您创建一个新表吗?

相反,我建议您使用MySQL Partitioning

这是对表进行分区的示例:

CREATE TABLE statistics (
  id INT AUTO_INCREMENT NOT NULL,
  user_id INT NOT NULL,
  PRIMARY KEY (id, user_id)
) PARTITION BY HASH(user_id) PARTITIONS 101;

这为您提供了定义一个逻辑表的好处,同时还将表划分为许多物理表,以便在查询分区键的特定值时更快地访问。

例如,当您像示例一样运行查询时,MySQL 仅访问包含特定 user_id 的正确分区:

mysql> EXPLAIN PARTITIONS SELECT * FROM statistics WHERE user_id = 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: statistics
   partitions: p1    <--- this shows it touches only one partition 
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 8
          ref: NULL
         rows: 2
        Extra: Using where; Using index

分区的 HASH 方法意味着将行按整数分区键的模数放置在分区中。这确实意味着许多 user_id 映射到同一个分区,但每个分区平均只有 1/N 的行数(其中 N 是分区数)。并且您使用恒定数量的分区定义表,因此您不必每次获得新用户时都对其进行扩展。

您可以选择最多 1024 个(或 MySQL 5.6 中为 8192 个)的任意数量的分区,但有些人报告说当分区数达到这么高时会出现性能问题。

建议使用质数分区。如果您的 user_id 值遵循某种模式(例如仅使用偶数),则使用质数分区有助于更均匀地分布数据。


重新评论您的问题:

我如何确定合理数量的分区?

对于 HASH 分区,如果你使用 101 个分区,就像我在上面的例子中展示的那样,那么任何给定的分区平均大约有 1% 的行。你说你的统计表有 3000 万行,所以如果你使用这个分区,每个分区只有 300k 行。这对 MySQL 来说更容易阅读。您也可以(并且应该)使用索引——每个分区都有自己的索引,它只有整个未分区表上的索引的 1%。

那么如何确定合理的分区数量的答案是:你的整个表有多大,你希望分区平均有多大?

分区的数量不应该随着时间的推移而增长吗?如果是这样:我怎样才能自动化呢?

如果您使用 HASH 分区,则不一定需要增加分区数。最终你可能总共有 300 亿行,但我发现当你的数据量增长几个数量级时,无论如何都需要一个新的架构。如果您的数据增长到那么大,您可能需要在多个服务器上进行分片以及分区到多个表中。

也就是说,您可以使用 ALTER TABLE 重新分区表:

ALTER TABLE statistics PARTITION BY HASH(user_id) PARTITIONS 401;

这必须重组表(就像大多数 ALTER TABLE 更改一样),所以预计需要一段时间。

您可能想要监控分区中数据和索引的大小:

SELECT table_schema, table_name, table_rows, data_length, index_length
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE partition_method IS NOT NULL;

与任何表一样,您希望活动索引的总大小适合您的缓冲池,因为如果 MySQL 在 SELECT 查询期间必须将部分索引交换进出缓冲池,则性能会受到影响。

如果您使用 RANGE 或 LIST 分区,则添加、删除、合并和拆分分区更为常见。请参阅http://dev.mysql.com/doc/refman/5.6/en/partitioning-management-range-list.html

我鼓励您阅读有关分区的手册部分,并查看这个不错的演示文稿:使用 MySQL 5.1 分区提高性能

于 2013-05-27T08:05:54.297 回答
5

这可能取决于您计划经常进行的查询类型,而确定的最佳方法是实现两者的原型并进行一些性能测试。

话虽如此,我希望带有索引的单个(大)表总体上会做得更好,因为大多数 DBMS 系统都经过大量优化,可以处理在大表中查找和插入数据的确切情况。如果您尝试制作许多小表以希望提高性能,那么您有点与优化器作斗争(这通常会更好)。

另外,请记住,一张桌子将来可能更实用。如果您想获得所有用户的汇总统计信息怎么办?拥有 20 000 个表会使执行起来非常困难且效率低下。这些模式的灵活性也值得考虑。如果您像这样对表格进行分区,您可能会将自己设计成未来的角落。

于 2013-05-23T19:03:01.873 回答
2

具体例子:

我有一张关于用户统计信息的表格(20,000 个用户和大约 3000 万行)。该表大约有 10 列,包括 user_id、actions、timestamps 等。最常见的应用是:通过 user_id 插入数据和通过 user_id 检索数据(SELECT 语句从不包含多个 user_id)。

做这个:

id INT UNSIGNED NOT NULL AUTO_INCREMENT,
 ...
PRIMARY KEY(user_id, id),
INDEX(id)

在PK开始user_id时拥有“参考位置”。也就是说,一个用户的所有行都聚集在一起,从而最大限度地减少 I/O。

PK末尾id是因为 PK 必须是唯一的。

长相怪异INDEX(id)是为了保持AUTO_INCREMENT快乐。

摘要问题:

  • 永远不要有多个相同的表。
  • PARTITIONing仅在满足http://mysql.rjweb.org/doc.php/partitionmaint中列出的用例之一时使用
  • PARTITIONed与非分区等效表相比,表需要一组不同的索引。
  • 在大多数情况下,单个非分区表是最佳的。
  • 使用查询来设计索引。
于 2019-11-26T02:33:26.240 回答
1

Bill Karwins 的回答几乎没有什么可补充的。但一个提示是:检查用户的所有数据是否一直都需要完整详细。

如果您想提供使用统计信息或访问次数或那些东西,您通常不会得到单个操作和秒的粒度,例如从今天的角度来看 2009 年。因此,您可以构建聚合表和存档表(当然不是引擎存档)以获取有关操作库的最新数据以及对旧操作的概述。

我认为旧的行为不会改变。

例如,您仍然可以使用归档表中的 week_id 从聚合中详细了解。

于 2013-05-27T09:24:02.423 回答
0

与每个用户从 1 个表变为 1 个表不同,您可以使用分区来达到中间某个位置的多个表/表大小比率。

您还可以保留有关用户的统计信息,以尝试将“活跃”用户移动到 1 个表中,以减少您必须随时间访问的表的数量。

底线是您可以做很多事情,但主要是您必须构建原型和测试,并评估您所做的各种更改对性能的影响。

于 2013-05-23T19:02:10.583 回答