临时表(内存引擎)和大型物理表(1.7GB myisam)之间的连接优化
Posted
技术标签:
【中文标题】临时表(内存引擎)和大型物理表(1.7GB myisam)之间的连接优化【英文标题】:Join optimization between Temporary table (Memory Engine) and large physical table (1.7GB myisam) 【发布时间】:2017-01-30 06:23:55 【问题描述】:我在对大型物理表(1.7GB,2300 万行)进行查询时遇到优化问题,我们称之为 p_table。
我需要使用 p_table 的主索引选择数千行。我的第一次尝试是使用主索引进行 IN 查询,例如
SELECT * FROM p_table WHERE primary_key IN (111,222,333,[... 60.000 more]).
由于查询速度非常慢(50-60 秒),我决定通过使用内存引擎将所有主键添加到临时表中来优化它,然后按如下方式加入
CREATE TEMPORARY TABLE tmp_table (primary_key BIGINT(20) NOT NULL PRIMARY KEY) ENGINE=Memory;
INSERT IGNORE INTO t_table VALUES(111),(222),(333),[thousandsmore];
SELECT p.* FROM p_table AS p FORCE INDEX(PRIMARY) INNER JOIN tmp_table AS t FORCE INDEX(PRIMARY) ON t.primary_key = g.primary_key;
此解决方案加快了 x4 的查询,但仍会导致服务器负载很大,每次查询大约需要 10-20 秒(取决于临时表的大小)。
查询的解释表明它没有使用索引,即使我强制它们。
["id"]=>
string(1) "1"
["select_type"]=>
string(6) "SIMPLE"
["table"]=>
string(1) "t"
["type"]=>
string(3) "ALL"
["possible_keys"]=>
string(7) "PRIMARY"
["key"]=>
NULL
["key_len"]=>
NULL
["ref"]=>
NULL
["rows"]=>
string(4) "64320"
["Extra"]=>
string(0) ""
不幸的是,整个数据库超过 50GB,这意味着我买不起完整的内存数据库,而且像 p_table 这样的大表依赖于磁盘 I/O。
您对如何优化流程有什么建议吗?还有关于为什么没有使用索引的任何提示(或者更有可能没有由 EXPLAIN 显示)?
服务器信息: Debian 8.6 mysql 5.5.53 8GB 内存 Raid0 中的 SSD 磁盘(它是 Slave 之一,Raid10 在 Master 上)
非常感谢
【问题讨论】:
***.com/help/someone-answers 【参考方案1】:为什么不使用索引
mysql 没有在表 t 上使用索引的原因是因为这完全没有必要。您的临时表上没有过滤器(别名为 t)因此您将临时表中的每一行与 p 连接起来。在这里使用索引没有任何好处。
来自手册:https://dev.mysql.com/doc/refman/5.7/en/mysql-indexes.html
MySQL 为这些操作使用索引:
快速找到匹配 WHERE 子句的行。
从考虑中排除行。如果可以选择 多个索引,MySQL 通常使用找到 最少的行数(最具选择性的索引)。
它不符合任何主要标准或上面列出的其他一些标准。但是,您的主键是覆盖索引,因此 mysql 确实可以选择使用索引而不是引用行。然而,这并没有带来任何特别的优势。因此决定不使用索引。
我实际上建议您删除临时表上的索引。这甚至可能会加快速度,因为将 60,000 行插入该表会更快。
你可以尝试的事情
看看是否可以对您的数据进行分区。例如,您插入到临时表中的 id 是否存在某种模式?看看你是否可以根据该模式进行分区
增加服务器上的内存。
使用更快的硬盘驱动器或将数据条带化到多个硬盘驱动器。
切换到可以在多个 CPU 内核上并行执行相同查询的 RDBMS。
【讨论】:
感谢您对索引的澄清。关于加快进程的任何提示?插入速度非常快,0.00001 秒,因此即使删除索引也不会影响整体性能。您认为还有其他方法可以达到更好的选择性能吗? 谢谢,但目前没有一个提议的解决方案是可行的。我正在寻找 Mysql 级别的优化。 条带化:由于工作是在单个线程中完成的,条带化没有任何好处。 如果您受 I/O 限制,并行性将无济于事。 @AndreaOlivato 您要求我们提供的解决方案,什么对您可行或不可行不属于规定条件的一部分。【参考方案2】:发生了什么
让我解释一下 MyISAM 索引在 这种 的情况下是如何工作的。有两种结构:
数据位于普通文件(.MYD)中。 每个索引(PRIMARY KEY
或辅助键)都位于该索引的 BTree 结构中(在 .MYI 文件中)。
IN( long list )
将向下钻取 BTree 60K 次。每次,它都会在底部找到 .MYD 文件的偏移量。然后它将从 .MYD 文件中随机获取。这可能每行获取两个磁盘命中。 (我假设BTree的非叶子节点被快速缓存,所以不要计算。)
每个磁盘命中可能由于缓存而被避免。
如果索引小于key_buffer_size
,那么BTree 看起来可能不必每次都访问磁盘。
要获取数据,它会要求操作系统从 .MYD 中读取数据,并让操作系统将其缓存在任何空闲空间中。此外,根据行的分散程度,“缓存”可能没有多大帮助。
由于这两个缓存区域是分开的,并且会争夺 RAM,因此我建议将 20% 的 RAM 用于 key_buffer,而将大部分 RAM 用于数据缓存。 (不知道您的详细信息,我不能说 20% 是否适合 您的 案例。)
当您添加 MEMORY
表时,您首先通过内存表查找要查找的 id 而不是直接在查询中查找,从而减慢了它的速度。但是,你说它跑得更快?这可能是由于缓存了从一个测试到下一个测试的变化。
此外,MEMORY 表从其他缓存中拿走了 RAM,但没有提供任何好处。
部分解决方案
有一种加快查询速度的方法。让我稍微解释一下 InnoDB 及其PRIMARY KEY
。 PK 与数据“聚集”在一起,这两个东西在磁盘上的同一个 BTree 中(然后缓存在 innodb_buffer_pool 中)。所以每行只有一个潜在磁盘命中。所以……
ALTER TABLE ... ENGINE=InnoDB;
shrink key_buffer_size and raise innodb_buffer_pool_size
SELECT ... IN ( 60K values )
您仍然会受到磁盘速度的影响,但它应该会更快。
其他
SSD 会比旋转驱动器更快。
PARTITIONIng
、“条带化”、临时表和并行性都无济于事(在这种情况下)。
如果您不需要BIGINT
的大范围(占用8 个字节),请切换到INT UNSIGNED
(4 个字节,0..4 亿)或MEDIUMINT UNSIGNED
(0..16M)。这将缩小大部分内容,从而使它们更易于缓存,因此 I/O 更少,因此速度更快。
【讨论】:
嗨瑞克,非常感谢您的建议。磁盘已经是 SSD(我在 Digital Ocean 上)。如果可能的话,肯定会尝试从 bigint 到更小的东西(需要检查我的最大 id)。将跟进结果 不幸的是我的最高键是“4547505810”因此它有点高于INT并且不能使用它,需要坚持使用BIGINT。还有其他建议吗? 请提供SHOW CREATE TABLE
。
还有SHOW TABLE STATUS LIKE 'p_table';
请在下方找到SHOW TABLE STATUS LIKE 'gender_1' \G Engine: MyISAM Version: 10 Row_format: Dynamic Rows: 23620723 Avg_row_length: 32 Data_length: 770509332 Max_data_length: 281474976710655 Index_length: 1120541696 Data_free: 0 Auto_increment: NULL Create_time: 2017-01-07 22:21:26 Update_time: 2017-02-01 16:52:21 Check_time: 2017-01-12 22:09:43 Collation: latin1_swedish_ci Checksum: NULL Create_options: Comment:
以上是关于临时表(内存引擎)和大型物理表(1.7GB myisam)之间的连接优化的主要内容,如果未能解决你的问题,请参考以下文章