处理巨大的 MyISAM table 以进行优化
Handling huge MyISAM table for optimisation
我有一个巨大的(并且还在增长)MyISAM table(7 亿行 = 140Gb)。
CREATE TABLE `keypairs` (
`ID` char(60) NOT NULL,
`pair` char(60) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM
table 选项已更改为 ROW_FORMAT=FIXED
,因为两列的长度始终固定为最大值 (60)。是的,ID
很遗憾是一个字符串,而不是一个 INT。
SELECT
查询速度效率还算可以。
数据库和mysql引擎都是127.0.0.1/localhost
。 (不远处)
可悲的是,INSERT
太慢了。我什至不谈论尝试 LOAD DATA
数百万新行...需要几天时间。
不会有任何并发读取。所有 SELECT 都由我的本地服务器一一完成。(它不是供客户端使用的)
(信息:文件大小 .MYD=88Gb,.MYI=53Gb,.TMM=400Mb)
- 我怎样才能加快插入 table 的速度?
- 分区那么大 table 会有帮助吗? (那怎么办?)
- 我听说 MyISAM 正在使用 "structure cache" 作为 .frm 文件。配置文件中的一行有助于 mysql 将所有 .frm 保存在内存中(在分区的情况下),它也有帮助吗?实际上,我的 .frm 文件只有 7 亿行只有 9kb)
- string shortenning/compress 函数... ID 字符串? (与 rainbow tables 相同的想法)即使它降低了允许的最大唯一 ID,我也永远不会达到 60chars 的最大值。所以也许这是一个想法?但在创建新的唯一 ID 之前,我必须检查 db ofc
中是否不存在缩短的字符串
- 与缩短 ID 字符串的想法相同,对 ID 使用 md5() 怎么样?在那种情况下,缩短字符串是否意味着更快?
首先,你的主键不是incrementable。
这意味着,粗略地说:在每次插入时,索引都必须重新平衡。
这么大的table难怪这么慢。
还有这样的引擎...
那么,对于第二个问题:保留 MyISAM 旧垃圾有什么意义?
例如,您不介意在发生事故时松开一两排(或十几排)?等等等等,甚至抛开当前 MySQL 维护者(Oracle Corp)明确不鼓励使用 MyISAM。
因此,以下是可能的解决方案:
1) 切换到Inno;
2) 如果不能交出char ID,则:
添加自增数字键并设置为primary - 这样索引就会聚簇,insert的开销会显着下降;
将您当前的密钥转为二级索引;
3) 如果可以 - 很明显
优化 table 要求您针对特定查询进行优化。除非您有特定的查询,否则您无法确定最佳优化策略。任何优化都会以牺牲其他类型的查询为代价来改进一种类型的查询。
例如,如果您的查询是 SELECT SUM(pair) FROM keypairs
(无论如何都必须扫描整个 table 的查询),分区将无济于事,只会增加开销。
如果我们假设您的典型查询是通过主键一次插入或选择一个键对,那么是的,分区可以提供很大帮助。这完全取决于优化器是否可以判断您的查询将在一个狭窄的分区子集(最好是一个分区)中找到它的数据。
还要确保调整 MyISAM。调整选项不多:
- 分配
key_buffer_size
尽可能高的空间来缓存索引。虽然我从未尝试过超过 10GB 的任何东西,但我不能保证 MyISAM 密钥缓冲区在 53GB(你的 MYI 文件的大小)时是 stable。
- Pre-load 键缓冲区:https://dev.mysql.com/doc/refman/5.7/en/cache-index.html
- 大小
read_buffer_size
和 read_rnd_buffer_size
适当地给出了您 运行 的查询。我不能在这里给出具体的值,你应该用你的查询测试不同的值。
- 如果您想加快 LOAD DATA INFILE 的速度,请将
bulk_insert_buffer_size
设置为大一些。默认为 8MB,我会尝试至少 256MB。我还没有尝试过那个设置,所以我不能说经验。
我尽量不使用 MyISAM。 MySQL 肯定是在试图弃用它。
...is there a mysql command to ALTER TABLE add INT ID increment column automatically?
是的,看我对
的回答
- 在执行
LOAD
之前对传入数据进行排序。这将提高 PRIMARY KEY(id)
. 的可缓存性
PARTITIONing
不太可能有帮助,除非 ID
. 有一些有用的模式
PARTITIONing
不会 帮助 single-row 插入或 single-row 由 ID
获取。
- 如果字符串不是 60 的恒定宽度,您说
CHAR
而不是 VARCHAR
是在浪费 space 和速度 .改变那个。
- MyISAM 的
FIXED
只有在有很多 'churn'(删除+插入,and/or 更新)时才有用。
- 更小意味着更可缓存意味着更少I/O意味着更快。
.frm
是CREATE TABLE
的编码;与本次讨论无关。
- 一个简单的 compress/zip/whatever 几乎总是会压缩长度超过 10 个字符的 text 字符串。它们可以无损地解压缩。你的琴弦是什么样子的? 60 个字符的英文文本将缩小到 20-25 个字节。
- MD5 是 "digest",而不是 "compression"。您无法 从其 MD5 中恢复字符串。反正转换成
BINARY(16)
. 需要16个字节
PRIMARY KEY
是一个 BTree。如果 ID
有点 "random",那么 'next' ID(除非输入已排序)很可能不会被缓存。不,BTree 不是一直重新平衡。
- 将
PRIMARY KEY
变成辅助键(在添加 AUTO_INCREMENT
之后)将 不会 加快速度——它仍然需要更新 BTree其中 ID
!
- 你有多少内存?对于你的情况,以及对于这个负载,将 MyISAM 的
key_buffer_size
设置为可用 RAM 的大约 70%,但不要大于 .MYI
文件。我推荐一个大的 key_buffer 因为那是随机访问发生的地方; .MYD
仅被附加到(假设您 从未 删除任何行)。
- 我们确实需要查看您的
SELECTs
以确保这些更改不会破坏其他地方的性能。
- 确保您使用的是
CHARACTER SET
latin1 或 ascii;使用 CHAR
. utf8 会浪费更多 space
切换到 InnoDB 将使 table(数据+索引)的磁盘 space 增加一倍,甚至三倍。因此,它可能会显示出来。但一个缓解因素是 PK 与数据 "clustered",因此您不会为插入的每一行更新两件事。请注意,key_buffer_size
应降低到 10M,innodb_buffer_pool_size
应设置为可用 RAM 的 70%。
(我的项目符号适用于 InnoDB,除非指定了 MyISAM。)
在使用InnoDB时,最好尝试在每个事务中插入1000行。小于这个数会导致更多的交易开销;不止于此会导致撤消日志溢出,从而导致不同形式的减速。
十六进制 ID
因为 ID
总是 60 个十六进制数字,声明它是 BINARY(30)
并通过 UNHEX(...)
打包并通过 HEX(ID)
获取。通过 WHERE ID = UNHEX(...)
进行测试。这将缩小数据约 25%,MyISAM 的 PK 将缩小约 40%。 (InnoDB 总体为 25%。)
只需转换为BINARY(30)
:
CREATE TABLE new (
ID BINARY(30) NOT NULL,
`pair` char(60) NOT NULL
-- adding the PK later is faster for MyISAM
) ENGINE=MyISAM;
INSERT INTO new
SELECT UNHEX(ID),
pair
FROM keypairs;
ALTER TABLE keypairs ADD
PRIMARY KEY (`ID`); -- For InnoDB, I would do differently
RENAME TABLE keypairs TO old,
new TO keypairs;
DROP TABLE old;
微型内存
只有 2GB 的 RAM,MyISAM-only 数据集应该使用 key_buffer_size=300M
和 innodb_buffer_pool_size=0
之类的东西。对于 InnoDB-only:key_buffer_size=10M
和 innodb_buffer_pool_size=500M
。由于 ID
可能是某种摘要,因此它将 非常 随机。小型缓存和随机密钥相结合意味着几乎每个插入都将涉及磁盘 I/O。我的第一个估计是插入 1000 万行大约需要 30 个小时。你有什么样的驱动器?如果您还没有 SSD,将会有很大的不同。
要加快 INSERTs
的另一件事是在 LOAD
开始之前按 ID
排序 。但是 UNHEX
会变得棘手。这是我推荐的。
- 创建一个 MyISAM table,
tmp
, ID BINARY(30)
和 pair
, 但 没有 个索引。 (不用担心key_buffer_size
;它不会被使用。)
LOAD
将数据转化为tmp
.
ALTER TABLE tmp ORDER BY ID;
这将对 table 进行排序。仍然没有索引。我认为,在没有证据的情况下,这将是一个文件排序,在这种情况下,它比 "repair by key buffer" 快得多。
INSERT INTO keypairs SELECT * FROM tmp;
这将通过按 ID
顺序将行馈送到 keypairs
来最大化缓存。
同样,我已经仔细地拼写了一些东西,因此无论 keypairs
是哪个引擎,它都能正常工作。我预计第 3 步或第 4 步花费的时间最长,但我不知道哪个。
我有一个巨大的(并且还在增长)MyISAM table(7 亿行 = 140Gb)。
CREATE TABLE `keypairs` (
`ID` char(60) NOT NULL,
`pair` char(60) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM
table 选项已更改为 ROW_FORMAT=FIXED
,因为两列的长度始终固定为最大值 (60)。是的,ID
很遗憾是一个字符串,而不是一个 INT。
SELECT
查询速度效率还算可以。
数据库和mysql引擎都是127.0.0.1/localhost
。 (不远处)
可悲的是,INSERT
太慢了。我什至不谈论尝试 LOAD DATA
数百万新行...需要几天时间。
不会有任何并发读取。所有 SELECT 都由我的本地服务器一一完成。(它不是供客户端使用的)
(信息:文件大小 .MYD=88Gb,.MYI=53Gb,.TMM=400Mb)
- 我怎样才能加快插入 table 的速度?
- 分区那么大 table 会有帮助吗? (那怎么办?)
- 我听说 MyISAM 正在使用 "structure cache" 作为 .frm 文件。配置文件中的一行有助于 mysql 将所有 .frm 保存在内存中(在分区的情况下),它也有帮助吗?实际上,我的 .frm 文件只有 7 亿行只有 9kb)
- string shortenning/compress 函数... ID 字符串? (与 rainbow tables 相同的想法)即使它降低了允许的最大唯一 ID,我也永远不会达到 60chars 的最大值。所以也许这是一个想法?但在创建新的唯一 ID 之前,我必须检查 db ofc 中是否不存在缩短的字符串
- 与缩短 ID 字符串的想法相同,对 ID 使用 md5() 怎么样?在那种情况下,缩短字符串是否意味着更快?
首先,你的主键不是incrementable。 这意味着,粗略地说:在每次插入时,索引都必须重新平衡。
这么大的table难怪这么慢。 还有这样的引擎...
那么,对于第二个问题:保留 MyISAM 旧垃圾有什么意义?
例如,您不介意在发生事故时松开一两排(或十几排)?等等等等,甚至抛开当前 MySQL 维护者(Oracle Corp)明确不鼓励使用 MyISAM。
因此,以下是可能的解决方案:
1) 切换到Inno;
2) 如果不能交出char ID,则:
添加自增数字键并设置为primary - 这样索引就会聚簇,insert的开销会显着下降;
将您当前的密钥转为二级索引;
3) 如果可以 - 很明显
优化 table 要求您针对特定查询进行优化。除非您有特定的查询,否则您无法确定最佳优化策略。任何优化都会以牺牲其他类型的查询为代价来改进一种类型的查询。
例如,如果您的查询是 SELECT SUM(pair) FROM keypairs
(无论如何都必须扫描整个 table 的查询),分区将无济于事,只会增加开销。
如果我们假设您的典型查询是通过主键一次插入或选择一个键对,那么是的,分区可以提供很大帮助。这完全取决于优化器是否可以判断您的查询将在一个狭窄的分区子集(最好是一个分区)中找到它的数据。
还要确保调整 MyISAM。调整选项不多:
- 分配
key_buffer_size
尽可能高的空间来缓存索引。虽然我从未尝试过超过 10GB 的任何东西,但我不能保证 MyISAM 密钥缓冲区在 53GB(你的 MYI 文件的大小)时是 stable。 - Pre-load 键缓冲区:https://dev.mysql.com/doc/refman/5.7/en/cache-index.html
- 大小
read_buffer_size
和read_rnd_buffer_size
适当地给出了您 运行 的查询。我不能在这里给出具体的值,你应该用你的查询测试不同的值。 - 如果您想加快 LOAD DATA INFILE 的速度,请将
bulk_insert_buffer_size
设置为大一些。默认为 8MB,我会尝试至少 256MB。我还没有尝试过那个设置,所以我不能说经验。
我尽量不使用 MyISAM。 MySQL 肯定是在试图弃用它。
...is there a mysql command to ALTER TABLE add INT ID increment column automatically?
是的,看我对
的回答- 在执行
LOAD
之前对传入数据进行排序。这将提高PRIMARY KEY(id)
. 的可缓存性
PARTITIONing
不太可能有帮助,除非ID
. 有一些有用的模式
PARTITIONing
不会 帮助 single-row 插入或 single-row 由ID
获取。- 如果字符串不是 60 的恒定宽度,您说
CHAR
而不是VARCHAR
是在浪费 space 和速度 .改变那个。 - MyISAM 的
FIXED
只有在有很多 'churn'(删除+插入,and/or 更新)时才有用。 - 更小意味着更可缓存意味着更少I/O意味着更快。
.frm
是CREATE TABLE
的编码;与本次讨论无关。- 一个简单的 compress/zip/whatever 几乎总是会压缩长度超过 10 个字符的 text 字符串。它们可以无损地解压缩。你的琴弦是什么样子的? 60 个字符的英文文本将缩小到 20-25 个字节。
- MD5 是 "digest",而不是 "compression"。您无法 从其 MD5 中恢复字符串。反正转换成
BINARY(16)
. 需要16个字节
PRIMARY KEY
是一个 BTree。如果ID
有点 "random",那么 'next' ID(除非输入已排序)很可能不会被缓存。不,BTree 不是一直重新平衡。- 将
PRIMARY KEY
变成辅助键(在添加AUTO_INCREMENT
之后)将 不会 加快速度——它仍然需要更新 BTree其中ID
! - 你有多少内存?对于你的情况,以及对于这个负载,将 MyISAM 的
key_buffer_size
设置为可用 RAM 的大约 70%,但不要大于.MYI
文件。我推荐一个大的 key_buffer 因为那是随机访问发生的地方;.MYD
仅被附加到(假设您 从未 删除任何行)。 - 我们确实需要查看您的
SELECTs
以确保这些更改不会破坏其他地方的性能。 - 确保您使用的是
CHARACTER SET
latin1 或 ascii;使用CHAR
. utf8 会浪费更多 space
切换到 InnoDB 将使 table(数据+索引)的磁盘 space 增加一倍,甚至三倍。因此,它可能会显示出来。但一个缓解因素是 PK 与数据 "clustered",因此您不会为插入的每一行更新两件事。请注意,key_buffer_size
应降低到 10M,innodb_buffer_pool_size
应设置为可用 RAM 的 70%。
(我的项目符号适用于 InnoDB,除非指定了 MyISAM。)
在使用InnoDB时,最好尝试在每个事务中插入1000行。小于这个数会导致更多的交易开销;不止于此会导致撤消日志溢出,从而导致不同形式的减速。
十六进制 ID
因为 ID
总是 60 个十六进制数字,声明它是 BINARY(30)
并通过 UNHEX(...)
打包并通过 HEX(ID)
获取。通过 WHERE ID = UNHEX(...)
进行测试。这将缩小数据约 25%,MyISAM 的 PK 将缩小约 40%。 (InnoDB 总体为 25%。)
只需转换为BINARY(30)
:
CREATE TABLE new (
ID BINARY(30) NOT NULL,
`pair` char(60) NOT NULL
-- adding the PK later is faster for MyISAM
) ENGINE=MyISAM;
INSERT INTO new
SELECT UNHEX(ID),
pair
FROM keypairs;
ALTER TABLE keypairs ADD
PRIMARY KEY (`ID`); -- For InnoDB, I would do differently
RENAME TABLE keypairs TO old,
new TO keypairs;
DROP TABLE old;
微型内存
只有 2GB 的 RAM,MyISAM-only 数据集应该使用 key_buffer_size=300M
和 innodb_buffer_pool_size=0
之类的东西。对于 InnoDB-only:key_buffer_size=10M
和 innodb_buffer_pool_size=500M
。由于 ID
可能是某种摘要,因此它将 非常 随机。小型缓存和随机密钥相结合意味着几乎每个插入都将涉及磁盘 I/O。我的第一个估计是插入 1000 万行大约需要 30 个小时。你有什么样的驱动器?如果您还没有 SSD,将会有很大的不同。
要加快 INSERTs
的另一件事是在 LOAD
开始之前按 ID
排序 。但是 UNHEX
会变得棘手。这是我推荐的。
- 创建一个 MyISAM table,
tmp
,ID BINARY(30)
和pair
, 但 没有 个索引。 (不用担心key_buffer_size
;它不会被使用。) LOAD
将数据转化为tmp
.ALTER TABLE tmp ORDER BY ID;
这将对 table 进行排序。仍然没有索引。我认为,在没有证据的情况下,这将是一个文件排序,在这种情况下,它比 "repair by key buffer" 快得多。INSERT INTO keypairs SELECT * FROM tmp;
这将通过按ID
顺序将行馈送到keypairs
来最大化缓存。
同样,我已经仔细地拼写了一些东西,因此无论 keypairs
是哪个引擎,它都能正常工作。我预计第 3 步或第 4 步花费的时间最长,但我不知道哪个。