我想知道什么是更高效和更快的性能:
在一个大表或多个没有索引的小表上有索引吗?
由于这是一个非常抽象的问题,让我更实用:
我有一个表有关于用户的统计信息(20,000个用户和大约3000万行)。该表包含大约10列,包括user_id
,actions
,timestamps
等
最常见的应用程序是:按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
,每个表中的数据都少得多,因此INSERT
和SELECT
语句应该更快。
现在再次提出问题:
处理这么多表(在我的情况下是20,000)而不是使用一个带有INDEX
的表吗?有任何现实世界的缺点吗?
我的方法实际上会加快速度吗,或者表格的查找最终会减慢速度而不是一切?
答案 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个表中,以减少您必须随时访问的表的数量。
最重要的是,您可以做很多事情,但主要是您必须构建原型和测试,并且只评估您正在进行的各种更改对性能的影响。