MySQL为什么要进行串行同步I / O?


8

当在MyISAM表上查看一个特别烦人的查询时,在许多情况下要花很长时间才能执行,我注意到MySQL似乎暴露出一种相当奇怪的I / O模式:执行单个查询时,必须要做很多事情I / O量(例如,对于表扫描或由于高速缓存而导致缓存为空,echo 3 > /proc/sys/vm/drop_caches因此需要首先从磁盘加载索引)时,基础块设备的队列大小接近于值1,而性能则极差仅4-5 MB / s:

root@mysql-test:~# iostat -xdm 5 /dev/sda
Linux 3.2.0-40-generic (mysql-test)  04/30/2014      _x86_64_        (4 CPU)

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.14    24.82   18.26   88.79     0.75     4.61   102.56     2.83   26.39   19.29   27.85   2.46  26.31

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00    69.29  151.52   72.73     5.31     0.59    53.95     1.21    5.39    7.84    0.29   4.39  98.51

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00   153.06  144.29  174.69     4.96     1.36    40.54     1.39    4.36    8.91    0.60   3.15 100.49

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00   105.75  150.92  109.03     4.53     0.85    42.41     1.29    4.96    8.15    0.54   3.90 101.36

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sda               0.00    48.89  156.36   51.72     5.28     0.76    59.38     1.28    6.16    8.02    0.55   4.77  99.23

虽然150 IOPS只是给定配置中单个磁盘能够提供的随机I / O能力,但结果仍然让我感到非常惊讶,因为我希望MySQL能够运行异步I / O进行读取和获取。同时处理大量块,而不是一一读取和评估它们,从而有效地忽略了RAID配置中可用的并行化增益。哪个设计决策或配置选项对此负责?这是特定于平台的问题吗?

当我使用大型的MyISAM表对此进行测试时,我看到了将相同表转换为InnoDB的类似效果(尽管还不错,但示例查询仍然需要20-30秒,而大部分时间都花在了读取磁盘上)重新启动mysql守护程序后,队列长度为1),因此缓冲池为空。我还验证了5.6 GA和当前的5.7里程碑14上仍然存在相同的问题-只要我使用单个查询线程,MySQL似乎就无法并行化查询处理所需的I / O操作。


根据要求,该方案还有一些其他详细信息。可以使用多种查询类型来观察该行为。我任意选择了一个进行进一步的测试,内容如下:

SELECT herp.id, herp.firstname, herp.lastname, derp.label, herp.email, 
(SELECT CONCAT(label, " (", zip_code, " ", city,")" ) FROM subsidiaries WHERE subsidiaries.id=herp.subsidiary_id ) AS subsidiary, 
(SELECT COUNT(fk_herp) from herp_missing_data WHERE fk_herp=herp.id) AS missing_data
FROM herp LEFT JOIN derp ON derp.id=herp.fk_derp
WHERE (herp.fk_pools='123456')  AND herp.city LIKE '%Some City%' AND herp.active='yes' 
ORDER BY herp.id desc LIMIT 0,10;

我知道它还有一些优化的余地,但是出于多种原因,我决定将其留在那儿,并集中精力为我看到的意外I / O模式找到一般的解释。

用过的表中确实有很多数据:

mysql> select table_name, engine, table_rows, data_length, index_length from information_schema.tables WHERE tables.TABLE_SCHEMA = 'mydb' and tables.table_name in ( 'herp', 'derp', 'missing_data', 'subsidiaries');
+-------------------------+--------+------------+-------------+--------------+
| table_name              | engine | table_rows | data_length | index_length |
+-------------------------+--------+------------+-------------+--------------+
| derp                    | MyISAM |      14085 |     1118676 |       165888 |
| herp                    | MyISAM |     821747 |   828106512 |    568057856 |
| missing_data            | MyISAM |    1220186 |    15862418 |     29238272 |
| subsidiaries            | MyISAM |       1499 |     6490308 |       103424 |
+-------------------------+--------+------------+-------------+--------------+
4 rows in set (0.00 sec)

现在,当我在这些表上运行上面的查询时,我得到的执行时间超过1分钟,而系统显然一直在忙于通过单个线程从磁盘读取数据。

示例查询执行的配置文件(在此示例中耗时1分9.17秒)如下所示:

mysql> show profile for query 1;
+--------------------------------+-----------+
| Status                         | Duration  |
+--------------------------------+-----------+
| starting                       |  0.000118 |
| Waiting for query cache lock   |  0.000035 |
| init                           |  0.000033 |
| checking query cache for query |  0.000399 |
| checking permissions           |  0.000077 |
| checking permissions           |  0.000030 |
| checking permissions           |  0.000031 |
| checking permissions           |  0.000035 |
| Opening tables                 |  0.000158 |
| init                           |  0.000294 |
| System lock                    |  0.000056 |
| Waiting for query cache lock   |  0.000032 |
| System lock                    |  0.000116 |
| optimizing                     |  0.000063 |
| statistics                     |  0.001964 |
| preparing                      |  0.000104 |
| Sorting result                 |  0.000033 |
| executing                      |  0.000030 |
| Sending data                   |  2.031349 |
| optimizing                     |  0.000054 |
| statistics                     |  0.000039 |
| preparing                      |  0.000024 |
| executing                      |  0.000013 |
| Sending data                   |  0.000044 |
| optimizing                     |  0.000017 |
| statistics                     |  0.000021 |
| preparing                      |  0.000019 |
| executing                      |  0.000013 |
| Sending data                   | 21.477528 |
| executing                      |  0.000070 |
| Sending data                   |  0.000075 |
| executing                      |  0.000027 |
| Sending data                   | 45.692623 |
| end                            |  0.000076 |
| query end                      |  0.000036 |
| closing tables                 |  0.000109 |
| freeing items                  |  0.000067 |
| Waiting for query cache lock   |  0.000038 |
| freeing items                  |  0.000080 |
| Waiting for query cache lock   |  0.000044 |
| freeing items                  |  0.000037 |
| storing result in query cache  |  0.000033 |
| logging slow query             |  0.000103 |
| cleaning up                    |  0.000073 |
+--------------------------------+-----------+
44 rows in set, 1 warning (0.00 sec)

您是否有可重复(理想情况下简单)的测试用例,您可以对其进行更详细的解释?例如产生这种行为的查询?什么情况下 您从“ echo 3> ...”和“重新启动mysql守护程序”开始了这条路,但没有详细介绍。
斯科特·利德利

@ScottLeadley感谢您对此进行调查。我认为我不能使其“简单”-仅当单个查询需要读取大量数据并且大部分是随机I / O时,该问题才可见。表和查询相当简单明了,尽管我可以发布DDL和查询文本,但我怀疑除非表/索引数据增长到数百兆字节,否则任何人都能够立即重现它。
syneticon-dj 2014年

正如您所暗示的,等待读取的5毫秒与一个5400 RPM磁盘的平均旋转延迟时间一致。读取“大量数据...主要是随机I / O”时寻求争用将解决此问题。至于RAID,您已经提到过,但没有提供此特定配置的任何详细信息。
斯科特·利德利

不确定我可以直接为您提供帮助,因为我没有运行您的配置。但是StackExchange的经验法则是,一个真正好的问题比赏金得到更多的关注。编写完美的问题
Scott Leadley 2014年

@ScottLeadley等待5毫秒主要是由于所使用的存储系统的延迟。我已经在不同的场景中对此进行了测试-从简单的4磁盘RAID10到具有16磁盘架和SSD支持的分层存储文件服务器,结果始终显示I / O负载没有并行化,因此延迟受限。我认为这根本是错误的。我已将查询详细信息添加到该问题中,但我尚不相信它们会带来很大帮助。
syneticon-dj 2014年

Answers:


8

首先,让我通过确认MyISAM不执行异步I / O来进行澄清,而在MySQL 5.5中,默认情况下InnoDB会执行异步I / O。在5.5之前的版本中,它通过工作线程使用“模拟的AIO”。

我认为区分以下三种情况也很重要:

  1. 一次执行多个查询
  2. 一个并行执行的查询
  3. 在进行表扫描/清除下一页众所周知的情况时,需要进行某种逻辑上的预读。

对于(1),I / O将能够为此并行执行。MyISAM有一些限制:表锁定和保护key_buffer(索引缓存)的全局锁定。MySQL 5.5+中的InnoDB确实在这里闪耀。

对于(2),目前不支持此功能。分区是一个很好的用例,您可以在其中并行搜索每个分区表。

对于(3),如果读取的页面超过56页(可配置),InnoDB可以进行线性预读以读取整个范围(64页的组),但是仍有进一步增强的空间。Facebook撰写了有关在其分支机构中实现逻辑读取头的文章(表扫描的性能提高了10倍)。


谢谢,这使我对所看到的内容有了更多的了解。这通常是否意味着MyISAM无法为单线程负载使用多个磁盘的IOPS?我在文档中找不到对此的任何引用-您碰巧有方便吗?
syneticon-dj 2014年

是。我想不出在文档中应该有的位置。
Morgan Tocker 2014年

2

我希望missing_data不是MyISAM,因为一个空的MyISAM表通常具有1024个字节.MYI。MyISAM应为非零字节大小。零字节.MYI对我来说有点令人毛骨悚然。

如果运行此元数据查询

select table_name, table_rows, data_length, index_length, engine
from information_schema.tables
WHERE tables.TABLE_SCHEMA = 'mydb'
and tables.table_name = 'missing_data';

而该表的引擎是MyISAM,则需要对其进行修复。

侧面注意:如果engineNULL,则为视图。如果是视图或不是MyISAM,请忽略我的其余帖子,并将该信息添加到问题中。如果表是MyISAM,请继续阅读...

根据您的元数据查询,missing_data.MYD大约为4600万。

首先,运行这个

SHOW CREATE TABLE mydb.missing_data\G

您将获得表描述或一条错误消息,内容如下:

ERROR 126 (HY000): Incorrect key file for table ...

如果您获得表描述,并且它是MyISAM,请运行

OPTIMIZE TABLE mydb.missing_data;

它将重新创建没有碎片的表,并计算新的索引统计信息。如果那不起作用,请尝试:

REPAIR TABLE mydb.missing_data;

那应该重新生成MyISAM的索引页。

为了安全起见(如果使用MySQL 5.6),请在修复后运行

FLUSH TABLES mydb.missing_data;

你的问题

如果MySQL查询优化器决定不使用表的索引,则可能不会将其索引加载到内存中。如果您的WHERE子句指示必须从索引中读取大量行,则MySQL Query Optimizer将在构造EXPLAIN计划时看到该行,并决定改用全表扫描。

MyISAM表上的并行I / O操作无法实现,因为它是不可配置的。

InnoDB可以进行调整以提高性能。


我必须再次强调它:如果mydb.missing_data是MyISAM并具有零字节索引,则肯定是错误的。
RolandoMySQLDBA'5

我更新了数据以使其更加连贯-现在它显示了来自单个主机的仅MyISAM结果,因此人们不会感到困惑。
syneticon-dj 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.