1

我的目标是在 MySQL 表中保存大约 6000 万行以进行高速读取,并正确地继续插入。

对于产品设计来说,这 6000 万行可以很自然的拆分成 3000 个块,所以我决定做一个分表策略,将一个 60M 的表拆分成 3000 个表。

我为以下测试获取了 300 万条数据:

  1. 1张表300万行:那么这300万条数据的平均插入时间为80秒,每1000次查询(每个查询从这张300万条数据表中提取1000行)大约需要10秒。

  2. 300 万行平均拆分为 3000 个表:将 300 万条数据插入 3000 个表:79 秒(不是真的更快);每 1000 次查询平均针对 3000 个表(其中每个表有 1000 行):120 秒(比上面慢 12 倍)

这是为什么?虽然我有 3000 张表,但基本上都是 MySQL 管理的文件,每次查询只打到一张只有 1000 行的表,但是为什么会那么慢呢?

我在具有以下配置的 15G RAM 的 8 核机器上运行:

open_files_limit 300000
table_open_cache 100000

经过2-3次模拟重试后,我也搜索了MySQL的“openED files”,如下所示,对于我的3000表设置似乎可以?

已打开表:9463

我怎样才能摆脱这个问题?

------------ 编辑和更多想法 ------------

我现在只是在尝试表分片的可能性,也许 MySQL Merge 引擎可以在这个方向上提供一点帮助。

另一方面,也许分区也不是一个坏主意……例如,以 MySQL 的 Range 分区为例,我可以将 Range 设置为 1000 万,然后 60M 的表变成具有 6 个分区的表……将查询和插入都更快?

----------- Trying Table Partition的更新-----------

正如下面所评论的那样,我在想可能 Table Partition 也可以是一个很好的解决方案,而不是 Table Sharding,特别是当它保持相同的表名并且对现有代码的影响最小时。

我试图在这个 6000 万张表上做 6 个分区;

1)起初,我做了一些看起来像下面的伪代码:

CREATE TABLE `datatable` (  
`id` int(11) NOT NULL AUTO_INCREMENT,  
`type` int(11) NOT NULL DEFAULT 0,  
`description` varchar(255),  
`datimeutc` datetime,  
`datimelocal` datetime,  
`value` double,  
PRIMARY KEY (`id`), 
KEY INDEX_TYPE ON (type)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1  
PARTITION BY RANGE (id) (  
    PARTITION p0 VALUES LESS THAN (10000000),  
    PARTITION p1 VALUES LESS THAN (20000000),  
    PARTITION p2 VALUES LESS THAN (30000000),  
    PARTITION p3 VALUES LESS THAN (40000000),  
    PARTITION p4 VALUES LESS THAN (50000000)  
    PARTITION p5 VALUES LESS THAN MAXVALUE
);

并且结果相当不错。导入 300 万条数据进行测试大约需要 1 分钟,导入全部 6000 万条数据总共需要 63 分钟。

每个查询的搜索时间(从基于 60-M 分区的表中获取 20000 行)大约为 90 毫秒。我没有针对单个 6000 万表的查询性能的任何比较数据,但是 90 毫秒是一个合理的值吗?

2)我尝试了“类型”字段上的分区,希望将传入的单个查询限制在单个分区上,因为 MySQL 对具有分区的唯一键有限制,伪代码如下所示:

CREATE TABLE `datatable` (  
`id` int(11) NOT NULL AUTO_INCREMENT,  
`type` int(11) NOT NULL DEFAULT 0,  
`description` varchar(255),  
`datimeutc` datetime,  
`datimelocal` datetime,  
`value` double,   
KEY (`id`), 
KEY INDEX_TYPE ON (type)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1  
PARTITION BY RANGE (type) (  
    PARTITION p0 VALUES LESS THAN (500),  
    PARTITION p1 VALUES LESS THAN (1000),  
    PARTITION p2 VALUES LESS THAN (1500),  
    PARTITION p3 VALUES LESS THAN (2000),  
    PARTITION p4 VALUES LESS THAN (2500)  
    PARTITION p5 VALUES LESS THAN MAXVALUE
);

这时候我插入60M的数据,插入时间比第一种情况要长。我还没有结果,但是到目前为止,只插入4M数据已经需要3个小时了……

这是为什么?

我在想,也许我是按顺序插入60M,即row Id从1开始到60000000。所以万一,我基本上打开并锁定第一个要插入的分区,一旦插入第一个10M,我打开第二个分区继续。

另一方面,在分区的情况2)中,我需要频繁随机打开所有6个分区(由'type'而不是'id'设计),所以表锁定和解锁花费了太多时间?这可能是原因吗?

4

2 回答 2

1

三千碎片?那太多了。mysqld 服务器不得不争先恐后地访问多个分片的数据文件,因此速度变慢了。

六千万行对于单个表来说是一个很大的数字,但对于你描述的服务器硬件来说并不算多。

在这样的应用程序中,分区的最重要原因是更容易快速删除大量过时的行。如果您的行有日期,您可以分区,例如,按月。

如果您必须对该表进行分片,请尝试使用四个分区。但除非您因性能需求而被迫分片,否则不要对其进行分片。如果我是你,我会让应用程序的其余部分正常工作。然后,一旦一切正常,我将评估所有系统的性能问题(瓶颈)并按严重程度处理它们。

我的直觉告诉我,这个大表不太可能导致严重的性能问题。

于 2013-07-04T17:38:18.760 回答
1

是的,在 MySQL 中拆分表是以下场景的一般良好做法:

  1. 表变得太大,常规表操作时间变得难以忍受(性能急剧下降)
  2. 表中热数据的百分比相对较小
  3. 数据有一个时间窗口(数据可以及时存档或清除)
  4. 为了提高并发性,在这种情况下,数据通常分布在各个独立的物理服务器或不同的存储系统中

在您的原始帖子中,我认为您主要关注第一种情况,所以让我们进行更多讨论。

为什么表很大时性能会急剧下降?什么是大小边界?一切都与记忆有关。除非您购买了 FusionIO 或任何类型的 SSD 系统,否则 I/O 命中磁盘时总会出现陡峭的曲线。通常 SATA/SAS 磁盘阵列只能执行大约 50~200 的随机 IOPS(写缓存受 BBU 保护),这与 DDR 的 200,000+ 随机 IOPS 相比太慢了。当 MySQL 的变量设置为合理的值并且表大小不大于缓存大小时,性能非常好,但是当表增长超过该限制时,就会发生退化。所以不要过度优化表结构,除非你知道它们会增长到多大,并在整个过程中测试了系统限制。太早的分表不会显示出太大的优势,性能甚至会变差,

基准就像游戏,你知道,它不能真正代表现实生活中的案例,所以我们需要规范游戏规则。我对您的 my.cnf 设置感到好奇,尤其是缓冲区变量,因为第一种情况的性能很大程度上取决于内存缓存和磁盘读/写策略。变量是:

  • table_definition_cache:这个变量指示有多少表元数据(对于 MyISAM,它们是 .frm 文件)可以存储在内存中。如果重复打开一个表将无济于事,但是如果此缓存可以包含所有表的元数据,则如果需要打开很多表(在您的情况下为 3000 个表)会有所帮助。
  • table_open_cache:这个变量表示 MySQL 可以在内存中保存多少个内部表处理程序,就像上面一样,它将提高表上下文切换速度。
  • key_buffer_size:由于您使用的是 MyISAM,因此此变量将在性能方面发挥非常重要的作用。它设置 MySQL 可以为 MyISAM 表分配的最大内存空间大小,如果您主要使用 MyISAM,则首选值是系统内存的 30%。为什么我拿了 30% 是因为有两个东西要缓存,一个是索引,一个是行数据;key_buffer_size 代表索引,OS 会处理行数据缓存(Block I/O Buffer Cache)。为索引保留 30%,为行数据保留 50%,为其余缓冲区缓存(如 table_*_cache、thread_cache、connection_cache 等)保留 20%。看起来这个变量不会减慢这两种情况,但谁知道呢,可能设置得太小了将遭受两种情况和多表遭受更多。
  • key_cache_block_size:此变量设置缓存块的大小,这将浪费 I/O(head/tail over reading)并导致 read-around writes(read before write)。多表场景可能会受到更多影响,因为它有更多的表(文件)。

我也很好奇 SQL 查询是如何编写的,你使用多少线程来读/写 MySQL。比如,顺序写一张表,感觉就像顺序写,速度比随机写快得多;顺序写3000张表感觉像随机写,速度可能不如相反。当创建 3000 个表时,有 3000 个 .MYI 文件和 3000 个 .MYD 文件,它们在磁盘上可能不连续(会发生随机 I/O),但是 1 个 .MYI 和 1 个 .MYD,它们很可能在磁盘上连续他们自己。这也适用于磁盘读取。但是在您的情况下,读取比写入慢得多,我认为这可能是因为写入被缓冲,但如果您是第一次选择行,则读取不会。并且当从一张表中读取时,MySQL可以将key_cache作为一个整体预加载一次,并且操作系统也可以预读下一个块,因为它们是连续的;但是在多表中,MySQL/OS 不能作为一个整体来做。如果您可以尝试产生更多的客户端线程来发出查询,那么这两种情况的性能可能会变得更接近。

关于您最近对分区的更新,是的,我认为您可能是对的,当您批量插入哪些 SQL 数据按主键排序但不是按“类型”排序时,按“类型”分区听起来很像随机 I/O,加上子-分区表处理程序开关。

于 2013-07-05T15:44:26.563 回答