MySQL性能:单个表和分区上的多个表与索引

时间:2013-05-23 18:55:34

标签: mysql performance indexing

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

由于这是一个非常抽象的问题,让我更实用:
我有一个表有关于用户的统计信息(20,000个用户和大约3000万行)。该表包含大约10列,包括user_idactionstimestamps等 最常见的应用程序是:按user_id插入数据并按user_id检索数据(SELECT语句从不包含多个user_id's)。

到目前为止,我INDEX上有一个user_id,查询看起来像这样

SELECT * FROM statistics WHERE user_id = 1

现在,随着越来越多的行,表变得越来越慢。由于INSERT变得越来越大,INDEX语句会变慢; SELECT语句慢下来,因为有更多行要搜索。

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

SELECT * FROM statistics_1

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

现在再次提出问题:
处理这么多表(在我的情况下是20,000)而不是使用一个带有INDEX的表吗?有任何现实世界的缺点吗? 我的方法实际上会加快速度吗,或者表格的查找最终会减慢速度而不是一切?

5 个答案:

答案 0 :(得分:80)

创建20,000个表是个坏主意。不久之后你需要40,000个表,然后更多。

我在我的书SQL Antipatterns中称这种综合症元数据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

我建议您阅读manual section on partitioning,并查看这个精彩的演示文稿:Boost Performance With MySQL 5.1 Partitions

答案 1 :(得分:4)

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

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

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

答案 2 :(得分:1)

Bill Karwins的回答几乎没有。但有一个提示是:检查用户的所有数据是否始终都是完整的详细信息。

如果您想提供使用情况统计信息或访问次数或这些内容,您通常会从今天的视图中获取单个操作和秒的粒度,例如2009年。因此,您可以构建聚合表和存档表(当然不是引擎存档),以获取有关基于操作的最新数据以及对旧操作的概述。

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

例如,您仍然可以使用archive-table中的week_id从聚合中详细说明。

答案 3 :(得分:1)

具体示例:

  

我有一张表,该表包含有关用户的统计信息(20,000个用户,总共约3000万行)。该表大约有10列,包括user_id,操作,时间戳等。   最常见的应用程序是:通过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表与非分区等效表需要一组不同的索引。
  • 在大多数情况下,单个未分区的表是最佳的。
  • 使用查询设计索引。

答案 4 :(得分:0)

每个用户从1个表到1个表的Intead,你可以使用分区在中间的某个地方点击多个表/表大小比例。

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

最重要的是,您可以做很多事情,但主要是您必须构建原型和测试,并且只评估您正在进行的各种更改对性能的影响。