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

Hor*_*ren 29 mysql indexing performance

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

由于这是一个相当抽象的问题,让我让它变得更实用:
我有一个包含用户统计信息的表(20,000个用户和大约3000万行).该表有10列,包括user_id,actions,timestamps
最常见的应用是:通过插入数据user_id和user_ID的检索数据(SELECT报表从不包含多个user_id's).

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

SELECT * FROM statistics WHERE user_id = 1
Run Code Online (Sandbox Code Playgroud)

现在,随着越来越多的行,表变得越来越慢.INSERT陈述减慢因为INDEX变得越来越大; SELECT语句慢下来,因为有更多行要搜索.

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

SELECT * FROM statistics_1
Run Code Online (Sandbox Code Playgroud)

哪里显然1代表user_id.
这样一来,没有INDEX需要,有远在每个表中的数据较少,所以INSERTSELECT声明要快很多.

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

Bil*_*win 82

创建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;
Run Code Online (Sandbox Code Playgroud)

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

例如,当您运行类似示例的查询时,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
Run Code Online (Sandbox Code Playgroud)

分区的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;
Run Code Online (Sandbox Code Playgroud)

这必须重组表(就像大多数ALTER TABLE更改一样),所以期望它需要一段时间.

您可能希望监视分区中的数据和索引的大小:

SELECT table_schema, table_name, table_rows, data_length, index_length
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE partition_method IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • @Stephan,是的,ALTER TABLE根据新的分区方案重建整个表. (2认同)

Ole*_*ksi 5

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

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

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