SystemTapでMySQL 5.5のDisk I/Oを分析する

http://d.hatena.ne.jp/sh2/20111121

2010年1月の記事SystemTapでMySQLのDisk I/Oを分析するの続きです。以前作成したSystemTapスクリプトは、実はMySQL 5.5のDisk I/Oを分析することができませんでした。というのも、MySQL 5.5からInnoDBが非同期I/Oを行うようになったのですが、以前のスクリプトは非同期I/Oに対応していなかったためです。本日はMySQL 5.5におけるInnoDBの非同期I/Oについて、確認していきたいと思います。

非同期I/Oとは

非同期I/Oとは、I/O処理をブロックされることなしに行う方式のことです。通常のI/O処理はそれが完了するまで待たされてしまうのですが、非同期I/Oを用いることでI/O処理の完了を待つことなしに他の処理を進めることができます。以下のウェブサイトでとても詳しく解説されています。

詳解 Linuxカーネル 第3版をお持ちの方は、16.4.1章「Linux 2.6における非同期I/O」も合わせてご覧ください。

MySQL 5.5のInnoDBにおける非同期I/Oの利用

InnoDBは元々Windowsでのみ非同期I/Oを行っており、それ以外のプラットフォームでは「simulated AIO」と呼ばれる擬似的な非同期I/Oを行っていました。これはSQLを実行するコネクションスレッドI/O要求をキューイングし、I/OヘルパースレッドI/O要求をキューから取り出して実際のI/Oを行うというものです。コネクションスレッドI/Oの完了を待たなくて良いという意味では確かに非同期ですが、I/Oヘルパースレッドが行う処理はあくまで同期I/Oであるため、この方式はsimulated AIOと呼ばれていました。I/Oヘルパースレッドの数はinnodb_read_io_threads、innodb_write_io_threadsという二つのパラメータで設定することが可能です。

MySQL 5.5のInnoDBでは、LinuxにおいてOSネイティブの非同期I/Oが利用されるようになりました。このためにはあらかじめlibaioパッケージをインストールしておく必要がありますが、設定パラメータinnodb_use_native_aioはデフォルトで有効になっており、何もしなければ自動的に非同期I/Oが利用されるようになっています。ただし現在のLinuxカーネルでは、Direct I/Oを利用している場合にのみ非同期I/Oが非同期になるという点に注意してください。つまり、非同期I/Oを利用するにはinnodb_flush_method = O_DIRECTの設定が必須となります。

非同期I/Oが有効化されている場合、コネクションスレッドI/O要求をキューイングするのではなく、直接非同期I/Oを発行するようになります。そしてI/Oヘルパースレッドは自分自身がI/O処理を行うのではなく、コネクションスレッドが行った非同期I/Oの完了を待機して後処理を行うという役割に変化します。具体的にはコネクションスレッドがio_submit(2)を行い、I/Oヘルパースレッドがio_getevents(2)を行うようになります。

これがどのようなメリットをもたらすかというと、I/Oヘルパースレッドの数を増やすことなしに一度に処理できるI/O要求の数を増やすことができるということになります。MySQL 5.1+InnoDB Pluginの構成においてinnodb_read_io_threadsとinnodb_write_io_threadsはそれぞれデフォルトで4であり、読み込みで4つ、書き込みで4つまでのI/O要求を一度に処理することができました。しかし、MySQL 5.5においてはOSストレージサブシステムが許す限りのI/O要求を一度に処理することができるようになります。例えばSATA Native Command Queuingでは、デバイスあたり最大32個のI/O要求をキューイングすることが可能となっています。

分析例

前回作成したSystemTapスクリプトを、非同期I/Oをプローブできるように改造しました。スクリプトは本エントリの末尾にあります。JdbcRunnerのTiny TPC-Cを用い、多重度16で負荷をかけたときのI/Oの様子を以下に示します。最初はMySQL 5.0.77のデータです。

-------- Synchronous I/O -------- ------- Asynchronous I/O --------
rTH   r/s  rKB/s wTH   w/s  wKB/s rTH   r/s  rKB/s wTH   w/s  wKB/s File
  0     0      0  17   164    629   0     0      0   0     0      0 /u02/mysql/ib_logfile1
  8     8    128   7    15  14496   0     0      0   0     0      0 /u02/mysql/ibdata1
 17   354   6992   1   160   2656   0     0      0   0     0      0 /u02/mysql/tpcc/customer.ibd
  0     0      0   1     5     80   0     0      0   0     0      0 /u02/mysql/tpcc/history.ibd
  2    22    464   0     0      0   0     0      0   0     0      0 /u02/mysql/tpcc/item.ibd
  6     8    128   1    12    224   0     0      0   0     0      0 /u02/mysql/tpcc/new_orders.ibd
 17    58    928   1    56   1008   0     0      0   0     0      0 /u02/mysql/tpcc/order_line.ibd
 10    20    320   1    14    240   0     0      0   0     0      0 /u02/mysql/tpcc/orders.ibd
 17  1057  20656   1   513   9264   0     0      0   0     0      0 /u02/mysql/tpcc/stock.ibd

rTHはそのファイルを読み込んだスレッドの数、r/sは1秒あたりの読み込み回数、rKB/sは1秒あたりに読み込んだ容量(KB)を表しています。非同期I/Oは行われておらず、全て同期I/Oであることが分かります。テーブルスペースファイルに対して読み込みを行うスレッドは最大でコネクションスレッド数+innodb_read_io_threadsの17個、書き込みを行うスレッドはinnodb_write_io_threadsの1つだけとなっています。

ここでお詫びですが、2009年の記事SSDの真の性能を引き出す MySQL 5.1.38 InnoDB Plugin - SH2の日記において、アーキテクチャの図が間違っていました。innodb_read_io_threadsは先読みだけを行うスレッドであり、通常のページ読み込みはコネクションスレッド自身が行います。コメント欄にあるid:mogwaingさんのご指摘が正しいです。どうもありがとうございました。描き直した図を以下に示します。

先読みというのは、ある程度連続したページの読み込みが要求された際に、フルスキャンだと判断して次に読まれるであろう先の方のデータをあらかじめ読み込んでおく仕組みのことです。詳しくはマニュアル7.7. Changes in the Read-Ahead Algorithmをご覧ください。

次はMySQL 5.1.60+InnoDB Pluginのデータです。

-------- Synchronous I/O -------- ------- Asynchronous I/O --------
rTH   r/s  rKB/s wTH   w/s  wKB/s rTH   r/s  rKB/s wTH   w/s  wKB/s File
  0     0      0  17   222    886   0     0      0   0     0      0 /u02/mysql/ib_logfile1
  6     6     96   4    12  11440   0     0      0   0     0      0 /u02/mysql/ibdata1
 16   458   7328   4   157   2528   0     0      0   0     0      0 /u02/mysql/tpcc/customer.ibd
  5    10    160   4    24    480   0     0      0   0     0      0 /u02/mysql/tpcc/history.ibd
  6     9    144   0     0      0   0     0      0   0     0      0 /u02/mysql/tpcc/item.ibd
  4     6     96   0     0      0   0     0      0   0     0      0 /u02/mysql/tpcc/new_orders.ibd
 17    70   1120   4    46    976   0     0      0   0     0      0 /u02/mysql/tpcc/order_line.ibd
 14    29    464   3    21    352   0     0      0   0     0      0 /u02/mysql/tpcc/orders.ibd
 16  1089  17424   4   385   7104   0     0      0   0     0      0 /u02/mysql/tpcc/stock.ibd

MySQL 5.0.77と比べると、テーブルスペースファイルに対して書き込みを行っているスレッドの数が増えていることが分かります。非同期I/Oは行われていません。

最後に、MySQL 5.5.18のデータです。

-------- Synchronous I/O -------- ------- Asynchronous I/O --------
rTH   r/s  rKB/s wTH   w/s  wKB/s rTH   r/s  rKB/s wTH   w/s  wKB/s File
  0     0      0  17   156    651   0     0      0   0     0      0 /u02/mysql/ib_logfile0
 16    40    640   7    18  17072   0     0      0   4    62    992 /u02/mysql/ibdata1
 16   306   4896   0     0      0   0     0      0   7   145   2320 /u02/mysql/tpcc/customer.ibd
  1     1     16   0     0      0   3     5     80   1     1     16 /u02/mysql/tpcc/history.ibd
 13    22    352   0     0      0   0     0      0   0     0      0 /u02/mysql/tpcc/item.ibd
  9    22    352   0     0      0   0     0      0   2    36    576 /u02/mysql/tpcc/new_orders.ibd
 16    80   1280   0     0      0   6    11    176   6   110   1760 /u02/mysql/tpcc/order_line.ibd
 11   109   1744   0     0      0   0     0      0   2    12    192 /u02/mysql/tpcc/orders.ibd
 16  1178  18848   0     0      0   0     0      0   7   701  11216 /u02/mysql/tpcc/stock.ibd

テーブルスペースファイルに対する書き込みが非同期I/Oになっていることが分かります。また先読みも非同期I/Oになったため、historyテーブルとorder_lineテーブルに対して先読みが行われていることが判別できます。

このように、MySQL 5.5でI/Oの仕組みが変わりました。ただ性能面ではMySQL 5.1+InnoDB Pluginの時点で既にかなり良くなっているため、そこからの改善幅はあまり大きくはないと思います。おそらく、よほど高性能なストレージを持ってこない限りI/O性能の違いは分からないでしょう。

SystemTapスクリプト

今回作成したSystemTapスクリプトを以下に示します。前回と同様Perlスクリプトとのセットになっているため、以下のようにパイプでつなげて実行してください。

# stap io2.stp | perl io2.pl

SystemTapスクリプトです。

global PROCNAME = "mysqld"
global fds

probe begin {
    printf("start\n")
}

probe syscall.read, syscall.pread {
    if (execname() == PROCNAME) {
        fds[pid(), tid()] = fd
    }
}

probe syscall.read.return, syscall.pread.return {
    if (execname() == PROCNAME && $return != -1) {
        printf("read\t%d\t%d\t%d\t%d\n", pid(), tid(), fds[pid(), tid()], $return)
    }
}

probe syscall.write, syscall.pwrite {
    if (execname() == PROCNAME) {
        fds[pid(), tid()] = fd
    }
}

probe syscall.write.return, syscall.pwrite.return {
    if (execname() == PROCNAME && $return != -1) {
        printf("write\t%d\t%d\t%d\t%d\n", pid(), tid(), fds[pid(), tid()], $return)
    }
}

probe syscall.io_submit {
    if (execname() == PROCNAME) {
        for (i = 0; i < nr; i++) {
            if ($iocbpp[i]->aio_lio_opcode == 0) {
                // IOCB_CMD_PREAD
                printf("aio_read\t%d\t%d\t%d\t%d\n",
                       pid(), tid(), $iocbpp[i]->aio_fildes, $iocbpp[i]->aio_nbytes)
            } else if ($iocbpp[i]->aio_lio_opcode == 1) {
                // IOCB_CMD_PWRITE
                printf("aio_write\t%d\t%d\t%d\t%d\n",
                       pid(), tid(), $iocbpp[i]->aio_fildes, $iocbpp[i]->aio_nbytes)
            }
        }
    }
}

probe syscall.close {
    if (execname() == PROCNAME) {
        printf("close\t%d\t%d\t%d\n", pid(), tid(), fd)
    }
}

probe timer.s(1) {
    printf("print\n")
}

集計用のPerlスクリプトです。

#!/usr/bin/perl

use strict;
use warnings;

my (%file_map, $do_print, %has_io, %rthreads, %rtimes, %rbytes, %wthreads, %wtimes, %wbytes,
    %aio_rthreads, %aio_rtimes, %aio_rbytes, %aio_wthreads, %aio_wtimes, %aio_wbytes);

$do_print = 0;

while (my $line = <>) {
    chomp($line);
    my ($call, $pid, $tid, $fd, $size) = split(/\t/, $line);

    if (($call eq ‘read‘) or ($call eq ‘write‘)
        or ($call eq ‘aio_read‘) or ($call eq ‘aio_write‘)) {

        if (!defined($file_map{"$pid,$fd"})) {
            $file_map{"$pid,$fd"} = readlink("/proc/$pid/fd/$fd") or next;
        }

        my $file = $file_map{"$pid,$fd"};

        if (substr($file, 0, 1) eq ‘/‘) {
            $do_print = 1;
            $has_io{$file} = 1;

            if ($call eq ‘read‘) {
                $rthreads{$file}->{$tid} = 1;
                $rtimes{$file}++;
                $rbytes{$file} += $size;
            } elsif ($call eq ‘write‘) {
                $wthreads{$file}->{$tid} = 1;
                $wtimes{$file}++;
                $wbytes{$file} += $size;
            } elsif ($call eq ‘aio_read‘) {
                $aio_rthreads{$file}->{$tid} = 1;
                $aio_rtimes{$file}++;
                $aio_rbytes{$file} += $size;
            } elsif ($call eq ‘aio_write‘) {
                $aio_wthreads{$file}->{$tid} = 1;
                $aio_wtimes{$file}++;
                $aio_wbytes{$file} += $size;
            }
        }

    } elsif ($call eq ‘close‘) {
        if (defined($file_map{"$pid,$fd"})) {
            delete($file_map{"$pid,$fd"});
        }

    } elsif ($call eq ‘print‘) {
        my ($sec, $min, $hour, $day, $mon, $year) = localtime();

        printf "%04d-%02d-%02d %02d:%02d:%02d\n",
               $year + 1900, $mon + 1, $day, $hour, $min, $sec;

        if ($do_print == 1) {
            print "-------- Synchronous I/O -------- ------- Asynchronous I/O --------\n";
            print "rTH   r/s  rKB/s wTH   w/s  wKB/s rTH   r/s  rKB/s wTH   w/s  wKB/s File\n";

            foreach my $file (sort keys %has_io) {
                printf "%3d %5d %6d %3d %5d %6d %3d %5d %6d %3d %5d %6d %s\n",
                       scalar(keys(%{$rthreads{$file}})),
                       defined($rtimes{$file})     ? $rtimes{$file}            : 0,
                       defined($rbytes{$file})     ? $rbytes{$file}     / 1024 : 0,
                       scalar(keys(%{$wthreads{$file}})),
                       defined($wtimes{$file})     ? $wtimes{$file}            : 0,
                       defined($wbytes{$file})     ? $wbytes{$file}     / 1024 : 0,
                       scalar(keys(%{$aio_rthreads{$file}})),
                       defined($aio_rtimes{$file}) ? $aio_rtimes{$file}        : 0,
                       defined($aio_rbytes{$file}) ? $aio_rbytes{$file} / 1024 : 0,
                       scalar(keys(%{$aio_wthreads{$file}})),
                       defined($aio_wtimes{$file}) ? $aio_wtimes{$file}        : 0,
                       defined($aio_wbytes{$file}) ? $aio_wbytes{$file} / 1024 : 0,
                       $file;
            }

            print "\n";

            $do_print = 0;
            %has_io = %rthreads = %rtimes = %rbytes = %wthreads = %wtimes = %wbytes = ();
            %aio_rthreads = %aio_rtimes = %aio_rbytes = %aio_wthreads = %aio_wtimes = %aio_wbytes = ();
        }

    } else {
        print "$call\n";
    }
}
时间: 2024-10-22 10:22:38

SystemTapでMySQL 5.5のDisk I/Oを分析する的相关文章

用systemtap对sysbench IO测试结果的分析1

http://www.actionsky.com/docs/archives/171 2016年5月6日  黄炎 近期在一些简单的sysbench IO测试中, 遇到了一些不合常识的测试结果. 从结果看, 虽然不是有实际意义的测试结果, 但探索过程中, 利用到了Systemtap进行观测(动态追踪), 可在其他分析中借用. 目录 1 测试环境准备 2 现象1 2.1 步骤1 2.2 结果1 2.3 分析1 2.4 插曲1 2.4.1 观测工具使用不当带来测试偏差 2.4.2 使用另一观测工具用于

MySQL学习系列2--MySQL执行计划分析EXPLAIN

1.Explain语法 EXPLAIN SELECT -- 变体:   EXPLAIN EXTENDED SELECT -- 将执行计划"反编译"成SELECT语句,运行SHOW WARNINGS 可得到被MySQL优化器优化后的查询语句 2.执行计划分析和实例 创建员工表Employee create table Employee ( ID int auto_increment, Ename varchar(32), Age int, Salary float, MID int, P

Mysql Join语法解析与性能分析详解

一.Join语法概述 join 用于多表中字段之间的联系,语法如下: ... FROM table1 INNER|LEFT|RIGHT JOIN table2 ON conditiona table1:左表:table2:右表. JOIN 按照功能大致分为如下三类: INNER JOIN(内连接,或等值连接):取得两个表中存在连接匹配关系的记录. LEFT JOIN(左连接):取得左表(table1)完全记录,即是右表(table2)并无对应匹配记录. RIGHT JOIN(右连接):与 LEF

MySQL查询大小写是否敏感问题分析

mysql数据库在做查询时候,有时候是英文字母大小写敏感的,有时候又不是的,主要是由mysql的字符校验规则的设置决定的,通常默认是不支持的大小写字母敏感的.  1. 什么是字符集和校验规则? 字符集是一套符号和编码.校对规则是在字符集内用于比较字符的一套规则.任何一个给定的字符集至少有一个校对规则,它可能有几个校对规则.要想列出一个字符集的校对规则,使用SHOW COLLATION语句. 校对规则一般有这些特征: 两个不同的字符集不能有相同的校对规则. 每个字符集有一个默认校对规则.例如,ut

mysql优化(三)–explain分析sql语句执行效率

mysql优化(三)–explain分析sql语句执行效率 mushu 发布于 11个月前 (06-04) 分类:Mysql 阅读(651) 评论(0) Explain命令在解决数据库性能上是第一推荐使用命令,大部分的性能问题可以通过此命令来简单的解决,Explain可以用来查看SQL语句的执行效 果,可以帮助选择更好的索引和优化查询语句,写出更好的优化语句. Explain语法:explain select … from … [where …] 例如:explain select * from

Mysql视图的作用及其性能分析

定义:视图是从一个或几个基本表导出的表,它与基本表不同,是一个虚表. 作用: 1.简化操作,不用进行多表查询. 2.当不同种类的用用户共享同一个数据库时,非常灵活,(用户以不同的 方式看待同一数据. 3.视图对重构数据库提供了一定程度的逻辑独立性. 数据的逻辑独立性是指:如增加新的关系或对原有的关系增加新的 字段,用户的应用程序不受影响. 例如:原有一个Student(Sno,Sname,Ssex,Sage,Sdept)这样一个表. 后来变动为:Sx(Sno,Sname,Sage)和SY(Sno

mysql慢查询日志切割,日志分析

#!/usr/bin/env python #coding:utf8 import os,sys import datetime,shutil,subprocess from config.config import slow_config class cut_mysql(object): mysql_user,mysql_pass,date_day,slow_log_name,slow_log_path,mysqlsla_log_name,mysqldumpslow_log_name,new_

MySQL中使用SHOW PROFILE命令分析性能的用法整理(配合explain效果更好,可以作为优化周期性检查)

这篇文章主要介绍了MySQL中使用show profile命令分析性能的用法整理,show profiles是数据库性能优化的常用命令,需要的朋友可以参考下 show profile是由Jeremy Cole捐献给MySQL社区版本的.默认的是关闭的,但是会话级别可以开启这个功能.开启它可以让MySQL收集在执行语句的时候所使用的资源.为了统计报表,把profiling设为1 mysql> SET profiling = 1; 之后在运行一个查询 mysql> SELECT COUNT(DIS

【转】由浅入深探究mysql索引结构原理、性能分析与优化

摘要: 第一部分:基础知识 第二部分:MYISAM和INNODB索引结构 1.简单介绍B-tree B+ tree树 2.MyisAM索引结构 3.Annode索引结构 4.MyisAM索引与InnoDB索引相比较 第三部分:MYSQL优化 1.表数据类型选择 2.sql语句优化 (1)     最左前缀原则 (1.1)  能正确的利用索引 (1.2)  不能正确的利用索引 (1.3)  如果一个查询where子句中确实不需要password列,那就用“补洞”. (1.4)  like (2)