2011年 5月 24日 はてなブックマーク -
タグ: #Mysql #innodb

今回は、MySQL-5.0.45のInnoDBで連番を管理するテーブルのパフォーマンス測定をしていたのですが、その際に少し変わったデッドロック問題に遭遇しましたので、そのあたりをネタとして書いてみたいと思います。

まずは、今回使用したデータベースのスキーマは下記のようなものです。

CREATE TABLE num (
    id bigint unsigned NOT NULL default '0'
) Engine=InnoDB;

AUTO_INCREMENTは使用していません。
そこに1レコードだけ登録します。

INSERT INTO num (id) values (1);

そして実際連番を取得する際には、

UPDATE num SET id = LAST_INSERT_ID(id+1);

といったクエリを発行しインクリメントしていき、最新のidはSELECTするのではなくUPDATE時のMySQL応答パケットに含まれるmysql_insertidを参照します。

上記のような内容をベンチマークテストのために同時接続を増やしながらテストしていると、350を超えたあたりで

ERROR 1213 (40001): Deadlock found when trying to get lock

というエラーが発生するという事態に遭遇しました(350という具体的な数値はハードウェアの性能などで変動すると思います)。ちなみに試験環境のOSやMySQLのバージョンは簡単ですが下記のような感じです。

  • MySQL-5.0.45
  • Linux-2.6.22

まずはWebで検索してみる

筆者の英語力不足という説もありますが、なかなか「コレ!」というものが見つけられませんでした。

パラメータやSQL文等を色々試してみる

  • 1カラムしかないのが逆に良くないのかと思い、主キーカラムを追加しWhere句で指定 → 変化なし
  • autocommitなのが良くないのかと思い、start transaction(またはbegin)とcommitを発行する → 変化なし
  • トランザクション分離レベルをserializableに変えてみる → 変化なし
  • innodb_table_locksパラメータを0にしてみる → 変化なし

他にもいくつか試しました。また、それらを組み合わせてみたりもしましたが解決しません。なので、ソースを読んでみることにします。

mixi Engineers’ Blog » MySQLのInnoDBでのデッドロック

結論からいいますと、サーバーのハードのスペックにもよりますが、今回の検証用環境では多少の増加は出来ましたが、さらに負荷を上昇させていくとロック待ち時間が増加し、その結果innodb_lock_wait_timeoutに引っかかり始めました。じゃあinnodb_lock_wait_timeoutも増加させればいいじゃないか、と考えもしましたが、ただ実際にはそれによりパフォーマンスが向上するわけではなく、「90秒や120秒待てばデッドロック扱いもタイムアウト扱いもせずSQLが完了します」というのは、MySQL的にはエラーでなくとも実際のシステム的にはエラーも同然ですのであまり意味がないのではないかと判断しました。というわけで、現在mysqldを動作させる一般的なハードウェア上では200という数字はそれなりに適切な値なのではないかと思いました。

ちなみにMyISAMではこのような問題は起こらず、パフォーマンスも良好な結果でした。システムの要件にもよりますが、MyISAMに変更しても問題ない場合はそちらも検討されるのも良いかと思います。

デッドロックになるはずのないSQL文でデッドロックだとエラーメッセージが返却された場合にこのような例もあったなということを思い出していただけると幸いです。
————-

MySQL5.1や5.5を使えばこの問題は起きないという噂です


1年前 | | 2011年 5月 24日 | このエントリーを含むはてなブックマーク
2011年 5月 24日 はてなブックマーク -
タグ: #Mysql

Lock wait timeout exceeded; try restarting transaction

出典: MySQL Practice Wiki

エラー概要

MySQLエラー番号 1205
SQLSTATE HY000
エラー名 ER_LOCK_WAIT_TIMEOUT
メッセージ Lock wait timeout exceeded; try restarting transaction
障害部位 InnoDBストレージエンジン
該当バージョン any
エラーの原因 他のセッションによりロックが長時間保持されていること。詳細は下記を参照。
対策 リトライ処理またはクエリの最適化。詳細は下記を参照。
備考

原因

ロック待ちのタイムアウトもある種のロックの競合であるが、デッドロックとは現象が異なる。(よく混同されるがまったくの別現象。)他のスレッドが保持しているロックをinnodb_lock_wait_timeout秒間待っても獲得出来なかった場合に発生する。

最適化されていないクエリによって発生することが多い。(SELECT … FOR UPDATEがフルテーブルスキャンしてしまった場合など。)

対処

トランザクションをリトライする必要があるので、InnoDB使用時はアプリケーションのロジックをそのように作り込むこと。

クエリを最適化して、長時間ロックを保持しないようにする。特にインデックスが適切に使用されることが重要。範囲検索やJOINをよく最適化しよう。

Lock wait timeout exceeded; try restarting transaction - MySQL Practice Wiki

innodb lock wait timeout のデフォルトは50秒。


1年前 | | 2011年 5月 24日 | このエントリーを含むはてなブックマーク
2011年 4月 16日 はてなブックマーク -

最近HandlerSocketの検証で手元のOSXにいれてみたので、その時の手順纏め。


MySQLはhomebrewでinstallしていることが前提
(いまさらmac portsはないよね?)


homebrewでインストールすると

~/Library/Caches/Homebrew

にinstallするときに使ったMySQLのソースコードがtar.gzでそのままあるので、
それをどっかの作業ディレクトリにコピーしましょう。

コーピーしたら、tarを解凍してconfigureします

$ ./configure --prefix=/usr/local LDFLAGS=-L/usr/local/lib CPPFLAGS=-I/usr/local/include --with-mysql=/tmp/mysql-5.1.55 CFLAGS="-I/usr/local/include/mysql -I/usr/local/include" CPPFLAGS="-I/usr/local/include/mysql -I/usr/local/include"



こんなん。

つぎにHandlerSocketの最新版をgithubから取得し、
configure/make/make installします

$ ./configure --with-mysql-source=/tmp/mysql-5.1.55 --with-mysql-bindir=/usr/local/bin
$ make
$ make install

これでinstall自体は終了しているので一度mysql.server startでmysqldを立ち上げます。

たちあげたmysqlに接続し

mysql> install plugin handlersocket soname 'handlersocket.so';

と一発打ち込みます。

その後、my.cnfにhandlersocketの設定を書いてmysqldを再起動すれば完了です。
ちゃんとhandlersocketが立ち上がってるかは

mysql> show processlist;
+----+-------------+-----------------+---------------+---------+------+-------------------------------------------+------------------+
| Id | User        | Host            | db            | Command | Time | State                                     | Info             |
+----+-------------+-----------------+---------------+---------+------+-------------------------------------------+------------------+
|  1 | system user | connecting host | NULL          | Connect | NULL | handlersocket: mode=rd, 0 conns, 0 active | NULL             |
|  2 | system user | connecting host | handlersocket | Connect | NULL | handlersocket: mode=wr, 0 conns, 0 active | NULL             |
|  3 | root        | localhost       | NULL          | Query   |    0 | NULL                                      | show processlist |
+----+-------------+-----------------+---------------+---------+------+-------------------------------------------+------------------+

と出ておればOKです。

https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-ja/installation.ja.txt

このあたりを参考にすればハマることなくinstallできるとおもいます。


最新のHandlerSocketではSQLのIN相当のことが出来るように変更を入れてもらったので、
より幅広い使い方ができますね!

OSXにHandlerSocketを入れる

1年前 | | 2011年 4月 16日 | このエントリーを含むはてなブックマーク
2011年 4月 4日 はてなブックマーク -
タグ: #MySQL

■チューニングを実施する上での前提知識
インデックスを理解する必要があります。データアクセス方法とその特徴をと
らえて正しいデータベースの扱い方を身につける必要があります。

▼デフォルト採用のB-Treeインデックス
MySQLのストレージエンジン(MyISAM,INNODB)のデフォルトインデックスとし
て採用されているのがB-Treeインデックスです。

B-Treeインデックスは、「ルートブロック」「ブランチブロック」「リーフブ
ロック」に分かれたツリー構造をしています。リーフブロックには、索引列の
列値でソートされた状態で格納されています。隣り合ったリーフブロックは、
お互いにリンクされており、このリンクをたどってすべてのリーフブロックの
データを並べると、全行分の索引列の列値をソートしたデータが得られます。

リーフブロックの上層であるブランチブロックには、配下のリーフブロックの
データの位置を示すデータブロックアドレスとリーフブロックに含まれる列値
の範囲が格納されます。

最上層のルートブロックにはルートブロックの配下のブランチブロックのアド
レスとブランチブロックに含まれる列値の範囲が格納されています。

B-Treeインデックスの特徴は、「ソート処理が高速」「複合インデックスが
作成できる」「均一した処理速度」「レンジ検索が可能」となります。

▼MySQLでの注意点
MySQLでB-Treeインデックスを作成する際の注意点があります。
以下に簡単な例示をしながら確認します。

(1)From句 で指定される1つのテーブルにつき1つのインデックスしか使用し
 ない。言い換えると、そのテーブルにアクセスする際は最適と思われる1つ
 のインデックスしか使用されない、ということになります。条件が複数ある
 ので、まずこのインデックスでアクセスして、次に異なるインデックスでア
 クセスして、最後にUNIONする、、、なんてことが必要でしたよね。

(2)!=、、およびlike後方一致条件には、インデックスは使用されない。
 これは、常識ですね。でも少し補足すると、このケースは2次インデックス
 の場合です。プライマリーインデックスでは”“でもインデックスを採用す
 る動きとなります。
 ★印に注目です。key項目は採用されたインデックスを表示します。

●col2に等価条件で、idx_test1を採用
mysql> explain select * from test1 where col2=’D’\G
*************************** 1. row *********************
id: 1
select_type: SIMPLE
table: test1
type: ref
possible_keys: idx1_test1
key: idx1_test1============================★
key_len: 137
ref: const
rows: 1
Extra: Using where
1 row in set (0.00 sec)

●col2’D’では、フルスキャンとなる
mysql> explain select * from test1 where col2’D’\G
*************************** 1. row *********************
id: 1
select_type: SIMPLE
table: test1
type: ALL
possible_keys: idx1_test1
key: NULL==================================★
key_len: NULL
ref: NULL
rows: 10
Extra: Using where
1 row in set (0.00 sec)

●プライマリーキー列のcol1の等価条件では、PRIMARYを採用
mysql> explain select * from test1 where col1=5\G
*************************** 1. row *********************
id: 1
select_type: SIMPLE
table: test1
type: const
possible_keys: PRIMARY
key: PRIMARY==================================★
key_len: 4
ref: const
rows: 1
Extra:
1 row in set (0.00 sec)

●プライマリーキー列のcol15条件でもPRIMARYを採用
mysql> explain select * from test1 where col15\G
*************************** 1. row *********************
id: 1
select_type: SIMPLE
table: test1
type: range
possible_keys: PRIMARY
key: PRIMARY==================================★
key_len: 4
ref: NULL
rows: 9
Extra: Using where
1 row in set (0.00 sec)

どうやら、プライマリーインデックスと2次インデックスで実行計画に違いが
出てくるようです。

(3)>、、Between、in、およびlike前方一致条件はレンジ検索される
 これも常識といってよいでしょう。
 
(4)複合インデックス作成時に指定するカラムの順序を意識する
 インデックスを構成する複数のカラムの先頭のカラムが SQLの条件に指定さ
 れなければ有効にならないということです。
 ここではさらに実行計画を確認します。
  ※各項目の説明は次回以降でじっくりとやります。
   ここではindexの使用、未使用をさらっと確認してください。
   ★印に注目です。key項目は採用されたインデックスを表示します。

●COL1で検索 → PK使用を確認
mysql> EXPLAIN SELECT * FROM TEST1 WHERE COL1=5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: TEST1
type: const
possible_keys: PRIMARY
key: PRIMARY==================================★
key_len: 4
ref: const
rows: 1
Extra:
1 row in set (0.00 sec)

●COL2で検索 → idx1_test1使用を確認
mysql> EXPLAIN SELECT * FROM TEST1 WHERE COL2=’D’\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: TEST1
type: ref
possible_keys: idx1_test1
key: idx1_test1==================================★
key_len: 137
ref: const
rows: 1
Extra: Using where
1 row in set (0.00 sec)

●COL3で検索 → インデックスを使用せずフルスキャンとなる
mysql> EXPLAIN SELECT * FROM TEST1 WHERE COL3=’D’\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: TEST1
type: ALL
possible_keys: NULL
key: NULL==================================★
key_len: NULL
ref: NULL
rows: 10
Extra: Using where
1 row in set (0.00 sec)

●COL4で検索 → インデックスを使用せずフルスキャンとなる
mysql> EXPLAIN SELECT * FROM TEST1 WHERE COL4=’D’\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: TEST1
type: ALL
possible_keys: NULL
key: NULL==================================★
key_len: NULL
ref: NULL
rows: 10
Extra: Using where
1 row in set (0.00 sec)

なお、★MySQL5.0以降に「index_merge」機能が付きました。上記に挙げた
「(1)From句で指定される1つのテーブルにつき1つのインデックスしか使用
 しない。」という制限がなくなっています。
 
さらに5.1系では・・・

パラメータ :’OPTIMIZER_SWITCH’
値 :’index_merge=on,index_merge_union=on,
  index_merge_sort_union=on,index_merge_intersection=on’

で制御することが可能です。

●index_mergeが採用されたケース
mysql> EXPLAIN SELECT * FROM TEST1 WHERE COL1=5 OR COL2=’g’\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: TEST1
type: index_merge
possible_keys: PRIMARY,idx1_test1
key: idx1_test1,PRIMARY==================================★
key_len: 137,4
ref: NULL
rows: 2
Extra: Using sort_union(idx1_test1,PRIMARY); Using where
1 row in set (0.00 sec)

■まとめ

MySQLのチューニングをする際の前提知識としてインデックスを理解する必要
があります。それは、データアクセス方法とその特徴をとらえて正しいデータ
ベースの扱い方を身につけることでもあります。

B-Treeインデックスの特徴は、「ソート処理が高速」「複合インデックスが
作成できる」「均一した処理速度」「レンジ検索が可能」となります。
さらに作成時の様々な注意点も考慮しながら必要最小限のインデックス管理
を行いましょう。

MySQLチューニング編 その1|技術情報|株式会社インサイトテクノロジー なお、ENGINE = MEMORYの時は特に指定が無ければHashインデックスとなります。 これはUSING BTREEといったものを使う事で解決出来ます。

MEMORY テーブル上に重複キーをもつハッシュインデックスがある場合(作成されるインデックスは同じ値を持つことが多い)、 キーの値に影響を与えるテーブルアップデートと、全ての消去は非常に処理が遅くなります。処理速度がどの程度落ちるかは、重複の度合いに比例します (或いは、インデックス濃度に反比例します。) 。BTREE インデックス を使用すれば、この問題は生じません。

MySQL :: MySQL 5.1 リファレンスマニュアル :: 13.7 MEMORY (HEAP) ストレージエンジン

=, >, >=、<, <=あるいはBETWEEN演算子を使用する表現のカラム比較に、B-treeインデックスが使用可能です。

ハッシュインデックスは先ほど挙げたインデックスとは特徴が異なります。

= or <=>演算子を使用する等価比較にのみ使用されます。(ただし非常に 高速です)<のように値の範囲を検索する比較演算子には使用されません。

MySQL :: MySQL 5.1 リファレンスマニュアル :: 6.4.5 MySQLにおけるインデックスの使用
参考: MySQL の MEMORY(HEAP) ストレージエンジンを使うときは index_type に気をつけろ - 刺身☆ブーメランのはてなダイアリー http://d.hatena.ne.jp/a666666/20100112/1263279871

1年前 | | 2011年 4月 4日 | このエントリーを含むはてなブックマーク
2011年 1月 26日 はてなブックマーク -
タグ: #MySQL

mysqladmin processlist で確認できる一般的な状態は以下のとおりです。

  • Checking table スレッドがテーブルの自動チェックを実行している。

  • Closing tables スレッドが、変更されたテーブルデータをディスクにフラッシュし、使用したテーブルを閉じている。これには通常それほど時間がかからない。時間がかかる場合、ディスクの使用率をチェックする必要がある。

  • Connect Out マスタに接続しているスレーブ。

  • Copying to tmp table on disk テンポラリ結果セットが tmp_table_size よりも大きく、スレッドがメモリベースのテンポラリテーブルをディスクベースに変更して、メモリの節約を図っている。

  • Creating tmp table スレッドは、クエリの結果の一部を保持するためのテンポラリテーブルを作成中。

  • deleting from main table 複数テーブルを削除する最初の段階で、最初のテーブルを削除中。

  • deleting from reference tables 複数テーブルを削除する 2 番目の段階で、他のテーブルから、一致したレコードを削除中。

  • Flushing tables スレッドが FLUSH TABLES を実行中。すべてのスレッドによりそのテーブルが閉じられるのを待っている。

  • Killed 誰かがスレッドを強制終了の命令を出したため、次回のキルフラグチェック時に強制終了される。MySQL では大きな各ループでフラグがチェックされるが、それでもスレッド終了には少し時間がかかる場合がある。スレッドが他のスレッドによってロックされている場合、そのロックが解除されたところで強制終了が実行される。

  • Sending data スレッドは SELECT ステートメントのレコードを処理中で、かつクライアントにデータを送信中。

  • Sorting for group スレッドは、GROUP BY のソートを実行中。

  • Sorting for order スレッドは、ORDER BY のソートを実行中。

  • Opening tables スレッドがテーブルを開こうとしている。これは、何かが妨害していなければすぐに終わるはずである。たとえば、ALTER TABLELOCK TABLE などにより、そのコマンドの終了時までテーブルが開かないことがある。

  • Removing duplicates クエリで SELECT DISTINCT が使用されたが、MySQL は初期段階で重複を除外する最適化を実行できなかった。このため、MySQL は結果をクライアントに送信する前に、重複レコードを削除する段階を踏む必要がある。

  • Reopen table スレッドはテーブルのロックを取得したが、ロック取得後、下のテーブル構造が変更されていることを認識した。このため、ロックを解除し、テーブルを閉じて、再び開こうとしている。

  • Repair by sorting 修復コードがソートを使用してインデックスを作成している。

  • Repair with keycache 修復コードが、キーキャッシュにより、キーを 1 つずつ作成している。これは、Repair by sorting よりも大幅に時間がかかる。

  • Searching rows for update スレッドがレコード更新の初期段階として、更新対象の一致レコードを検索中である。レコード検索に使用するインデックスを UPDATE が変更すると、この段階が必要となる。

  • Sleeping スレッドが、クライアントから新しいコマンドが送信されるのを待っている。

  • System lock スレッドが、テーブルの外部システムロックを待っている。同じテーブルにアクセスする複数の mysqld サーバを使用していない場合、--skip-external-locking オプションでシステムロックを無効にできる。

  • Upgrading lock INSERT DELAYED ハンドラが、レコード挿入のためにテーブルをロックしようとしている。

  • Updating スレッドが更新対象レコードを検索して更新している。

  • User Lock スレッドが GET_LOCK() を待っている。

  • Waiting for tables 下のテーブル構造が変更されているため、テーブルを開き直して新しい構造を取得する必要があるという通知をスレッドが受け取った。テーブルを開き直すためには、他のすべてのスレッドがそのテーブルを閉じるのを待つ必要がある。

    この通知は、他のスレッドがそのテーブルに対して FLUSH TABLESFLUSH TABLES table_nameALTER TABLERENAME TABLEREPAIR TABLEANALYZE TABLEOPTIMIZE TABLE のいずれかを実行している場合に発生する。

  • waiting for handler insert INSERT DELAYED ハンドラがすべての挿入処理を完了し、新規の挿入を待機中である。

ほとんどの状態はすぐに終わります。何秒も同じ状態が続く場合は、問題のある可能性があるので、調査が必要です。

第4章 データベース管理

1年前 | | 2011年 1月 26日 | このエントリーを含むはてなブックマーク
2011年 1月 16日 はてなブックマーク -

ソーシャルゲームのためのデータベース設計 - Presentation Transcript

本日のテーマ

  • データベース的な観点でのソーシャルゲームの特徴
  • データモデル
  • ソーシャルゲームに従来型RDBMSを使うべきか、 流行りのNoSQLで行くべきか
  • 負荷対策 (アーキテクチャ面)
  • 負荷対策 (ツール面)

ソーシャルゲームのためのデータベース設計


1年前 | | 2011年 1月 16日 | このエントリーを含むはてなブックマーク
2011年 1月 7日 はてなブックマーク -

SQLの「LEFT JOIN」やら「INNER JOIN」ってなんじゃろ?という方にとっても参考になりそうなサイトのご紹介。それぞれの命令をベン図で良い感じに表現しちゃっていますよ。とってもわかりやすいです。

Table AとTable Bが「name」というフィールドでつながっているという設定です。

  • INNER JOIN
  • まずはINNER JOIN。共通部分だけとってきます。

SELECT * FROM TableA
INNER JOIN TableB
ON TableA.name = TableB.name

図だとこんな感じ。

  • FULL OUTER JOIN
  • 次に重複部分はひとつだけにして、あと全部。

    SELECT * FROM TableA
    FULL OUTER JOIN TableB
    ON TableA.name = TableB.name
    

    図だとこんな感じね。

  • LEFT OUTER JOIN
  • 左側だけいっときます。

    SELECT * FROM TableA
    LEFT OUTER JOIN TableB
    ON TableA.name = TableB.name
    

    図。

  • 片方だけ
  • 上のから共通部分だけ除きます。

    SELECT * FROM TableA
    LEFT OUTER JOIN TableB
    ON TableA.name = TableB.name
    WHERE TableB.id IS null
    

    図ね。

  • 共通部分を除く
  • 次に全体から共通部分を除くにはこうします。

    SELECT * FROM TableA
    FULL OUTER JOIN TableB
    ON TableA.name = TableB.name
    WHERE TableA.id IS null
    OR TableB.id IS null
    

    図でいうとこんな感じ。

  • CROSS JOIN
  • これは図にできないのですが、重複しているものも含めて全部とってくる命令です。

    SELECT * FROM TableA
    CROSS JOIN TableB
    

    いかがですかね。JOIN文を書くときには参考にしてみたいですね。なお、サンプルデータで抽出結果がどうなるかを知りたい方は元サイトをどうぞ。

    » Coding Horror: A Visual Explanation of SQL Joins

    SQLのJOIN文を視覚的に理解する - IDEA*IDEA ~ 百式管理人のライフハックブログ

    1年前 | | 2011年 1月 7日 | このエントリーを含むはてなブックマーク
    2011年 1月 4日 はてなブックマーク -
    タグ: #MySQL #InnoDB #MyISAM
    copy to tmp table

    まず、カラム追加時に最初に行なわれる一時テーブルへのコピーについて。これは、「ALTER TABLE」を実行すると確実に行なわれる動作のようです。また、実行中対象テーブルは読み取り可、書き込み不可の状態になります。

    ALTER TABLE の処理では、元のテーブルの一時的なコピーが作成されます。 変更はこのコピーに対して実行されます。その後元のテーブルが削除され、新しいテーブルの名前が変更されます。この変更処理は、すべての更新が、エラーに なることなく、確実に新しいテーブルに自動でリダイレクトされるように実行されます。ALTER TABLE の実行中、元のテーブルは他のクライアントによって読み取り可能です。このテーブルの更新とテーブルへの書き込みは、新しいテーブルの準備が整うまで停止 されます。


    repair with keycache

    この動作は、「LOAD DATA INFILE」とかで大量のデータをinsert する処理を高速に行いたい場合に使われる手法と同じです。insert 毎にインデックスの更新を行なうと効率が悪いので、「ALTER TABLE … DISABLE KEYS」でインデックスの更新を行わないようにし、データを挿入。その後、「ALTER TABLE … ENABLE KEYS」でインデックスを再構築します。

    ALTER TABLE を実行する場合はこの動作が自動で行なわれ、インデックス構築の処理を高速化するとあります。

    MyISAM テーブルに対して ALTER TABLE を使用すると、非ユニークなインデックスのすべてが別のバッチに作成される(REPAIR の場合と同様)。 インデックスが数多くある場合は、これによって ALTER TABLE の処理がはるかに迅速化される。


    ただ、この「repair with keycache」には問題があるようです。


    repair with keycache ではなく、repair by sorting を使うようにする

    インデックスの再構築処理には「repair with keycache」を使う方法と、「repair by sorting」を使う方法があるようです。

    ALTER TABLE … ENABLE KEYSステートメントをMyISAMテーブルに対して実行する場合やインデックスの修復を行う際、MyISAMは以下の2通りのうちいずれかの方法によりインデックスの修復を試みる。

    • Repair by sorting
    • Repair with keycache

    名前からすると後者の方が速そうだが、実は前者の方が遙かに速い。


    Repair with keycache 修復コードが、キーキャッシュにより、キーを1つずつ作成している。これは、Repair by sorting よりも大幅に時間がかかる。


    インデックスの再構築を行なっている時に、「repair with keycache」が使われるのはよくないようです。高速な「repair by filesort」を使うようにするには、パラメータの設定が必要。適切なパラメータが設定されていないと、MySQL の判断によって「repair with keycache」が使われるようです。

    名前からすると後者(Repair with keycache )の方が速そうだが、実は前者(Repair by sorting)の方が遙かに速い。ただしこの手法を用いるにはソート用のバッファが確保されている必要があるのだが、これが実にやっかいである。バッ ファのサイズがとてつもなく大きいのだ。バッファはmyisam_max_sort_file_sizeにて指定するが、必要な分だけ確保するようになっ ているので100Gぐらいにしておいて差し支えないだろう。ファイルシステムがいっぱいになったり、ここに指定した値を超えてバッファが必要な場合には、 Repair with keycacheに自動的に切り替わる。

    myisam_max_sort_file_sizeだけでなく、myisam_sort_buffer_sizeを増やしておくとメモリ上 でバッファリングが行われるので、Repair by sortingがさらに高速になる。REPAIR TABLEやLOAD DATAで非常に大きなテーブルを扱う場合、myisam_sort_buffer_sizeを増やしておくと良いだろう。


    100G くらい割りあてるといいと書いてあるけど、パーティションとかの関係で/tmp にそんなに容量を割りあてていない場合結局「repair with keycache」が使われるので、「tmpdir」の値も変更してあげる必要があります。例えば、「/home」の容量に余裕があれば以下のような感 じ。

    $ mkdir /home/luke/tmp

    $ sudo /etc/init.d/mysql restart —myisam_max_sort_file_size=100G —tmpdir=/home/luke/tmp


    これで、カラムやインデックスの追加を高速に行なうことができるようになります。

    なお、「myisam_max_sort_file_size」や「myisam_sort_buffer_size」は今回のようなインデックスの作成時にしか使用されないバッファなので、通常は大きく割りあてておく必要はなさそうです。

    大きめのテーブルにカラムやインデックスを追加する際の注意 - Slow Dance

    mysqldumpしたデータが重たいのならば、そのsqlファイルの冒頭に
    「SET GLOBAL xxxxx=100G; 」のような指定を行っても良いですね。


    1年前 | | 2011年 1月 4日 | このエントリーを含むはてなブックマーク
    2011年 1月 4日 はてなブックマーク -
    タグ: #MySQL #InnoDB #MyISAM

    先日大きめ(といっても500万行くらい)のテーブルにインデックス付きのカラムを追加しようとして痛い目にあったので調査。


    大きめのテーブルにカラムやインデックスを追加するとどうなるか

    今回は単純に、「ALTER TABLE 〜 」で追加しようとしました。追加するカラムは3つで、

    • varchar(255)
      • インデックスなし
    • varchar(255)
      • ↓のdate 型カラムとマルチカラムインデックスの形式のユニークインデックスあり
    • date
      • インデックスあり

    SQL を実行し、状況を「SHOW PROCESSLIST」で監視していたら、1つ目のカラム追加で次のような状態に…

    • 最初にState が「copy to tmp table」状態になり、次の状態に遷移するまで1時間かかる
    • 次にState が「Repair with keycache」状態になり、完了までに1時間かかる
    • 次のカラム追加に対する「copy to tmp table」が開始される

    もう超絶的に遅いです。まぁ、1時間とかは序の口のようで、7時間かかっても終わらないこともあるらしい

    たかだか700万行、 mysqldump して 150MB 弱のデータ量しかないテーブルに一つユニークインデックスを貼るだけで7時間半かかってまだ終わらないってどういうことよ。この大きさになるともう ALTER TABLE では無理だってことか・・・。


    ただ、今回の場合監視していて不思議だったのが、top でmysql の状態を見ると全然仕事をしていなかったことです。CPU 使用率も2%とかで、おい!って感じ。


    解決策

    結論から言うと、「key_buffer_size」の不足でした…。「repair with keycache」を実行する際にも、MyISAM のインデックスのキャッシュ機構が使われるわけで、このサイズが足りなかったわけです。

    インデックスブロックには、key cache (あるいはkey buffer)という特別な構造が保持されています。その構造には最も頻繁に使用されるインデックスがおかれた多数のブロックバッファが含まれます。

    キーキャッシュのサイズを制限するには、key_buffer_sizeシステム変数を使用します。


    実際に追加している最中に確認したら驚きの少なさ!

    mysql> show variables LIKE '%key_buffer%';
    +-----------------+-----------+
    | Variable_name   | Value     |
    +-----------------+-----------+
    | key_buffer_size | 8384512   |
    +-----------------+-----------+
    

    なんとデフォルトのサイズ。MyISAM 使っていて、「key_buffer_size」の値を確認していないとはどこの小学生だよって感じです…( ゚Д゚)

    というわけで、直ぐに変更。マシンのメモリが1G だったので1/4 の256M に設定。クエリ実行中だったのでSET 構文使う。

    SET GLOBAL key_buffer_size=268435456; 	
    

    これを設定したら、mysqld が仕事し始めました。CPU も7~9割使っているし、メモリも割りあてた分をフルに使っている模様。さっきまでは使用できるメモリが小さすぎてディスクを使っていたのでIO 待ちが長くCPU を有効に使えていなかったのだと思います。


    結果、最初のカラム追加に2時間かかったのに対し、15分ほどで追加できるようになりました。これで、無事カラム追加を完了させることができました。


    最後に、SET 構文で設定した値はmysqld を再起動すると無効になってしまうので設定ファイルで値を変更して完了。

    大きめのテーブルにカラムやインデックスを追加する際の注意 - Slow Dance

    1年前 | | 2011年 1月 4日 | このエントリーを含むはてなブックマーク
    2011年 1月 4日 はてなブックマーク -
    タグ: #MySQL #InnoDB #MyISAM
    
    
    大量データのinsert

    もう、mysql_dumpの中見てもらえれば良いと思うけど、

    -- 以下みたいに書くより
    insert into x ( ... );
    insert into x ( ... );
    insert into x ( ... );
    insert into x ( ... );
    
    -- この方が良いよ
    insert into x values( ... ),( ... ),( ... ),( ... );
    
    indexの再構築が遅い

    一度に大量に突っ込むとindexの再構築が遅いです。

    そんな一度に大量のデータ突っ込んだりとかするようなジャンクな使い方する場合には、MyISAM使ってると思うので*1、MyISAMの場合にはこうしましょう。

    alter table x disable keys;
    insert into x values( ... ),( ... ),( ... ),( ... );
    insert into x values( ... ),( ... ),( ... ),( ... );
    insert into x values( ... ),( ... ),( ... ),( ... );
    -- ( 以下ry 多分100万回ループな感じで)
    alter table x enable keys;
    

    で、enable keys したタイミングで3日待たされたとかになっちゃう可能性が有ります。

    id:ichii386 (いちい君) がここで言及してますが

    http://d.hatena.ne.jp/ichii386/20070924/1190610994 

    myisam_max_sort_file_size と言う項目を広げてやるとソートにかかるコストが減るので速くなります。

    更新してなかった間にしてたこと | 404 motivation not found

    以下2つを1Gとかにすると「alter table x enable keys;」の実行結果が半日かかってこないような状況でも、その半分以下で終わったりするようになります。
    myisam_max_sort_file_size
    myisam_sort_buffer_size


    1年前 | | 2011年 1月 4日 | このエントリーを含むはてなブックマーク
    2010年 12月 20日 はてなブックマーク -
    タグ: #MySQL #Munin
    LordElph’s Ramblings&#160;» Munin plugin for graphing MySQL slave delay

遅延状況をモニタするMuninプラグイン

    LordElph’s Ramblings » Munin plugin for graphing MySQL slave delay

    遅延状況をモニタするMuninプラグイン

    1年前 | | 2010年 12月 20日 | このエントリーを含むはてなブックマーク
    2010年 12月 9日 はてなブックマーク -
    タグ: #MySQL

    FEDERATED

    5.0.3から追加されたストレージエンジンで、その用途は他のMySQLのテーブルにアクセスできるようにするためのもの(他のRDBMS でいうところのDBLINKのようなもの)です。今回紹介するストレージエンジンの中では『使える』部類に入るものではないかと思います。また、 FEDERATEDのユニークな点は、現在はMySQLとしか連携できませんが、将来的に他のRDBMS(PostgreSQLやOracleなど)との 連携も計画されていることが挙げられるでしょう。

     

    他のテーブルを参照する

    では早速、試してみましょう。以下ではFEDERATEDテーブルを作るホストをmy5-1、FEDERATEDで参照されるホストをmy5-2とし、下準備として図4のSQLを実行しておきます。

    <図4:FEDERATEDを使う準備>
    ●my5-2でrootユーザで実行するSQL CREATE DATABASE wd; GRANT SELECT, INSERT, UPDATE, DELETE , CREATE, ALTER, DROP, REFERENCES, INDEX , LOCK TABLES ON wd.* TO wd@'%' IDENTIFIED BY 'press2'; ●my5-2でwdユーザで実行するSQL use wd; DROP TABLE IF EXISTS user_master; CREATE TABLE IF NOT EXISTS user_master ( id SMALLINT UNSIGNED ,name VARCHAR(32) NOT NULL ,age TINYINT UNSIGNED ,PRIMARY KEY (id) ) ENGINE=InnoDB; INSERT INTO user_master VALUES (1,'ichirou', 21) ,(2,'jirou', 32) ,(3,'saburou', 43) ; ●my5-1でrootユーザで実行するSQL CREATE DATABASE wd; GRANT SELECT, INSERT, UPDATE, DELETE , CREATE, ALTER, DROP, REFERENCES, INDEX , LOCK TABLES ON wd.* TO wd@'%' IDENTIFIED BY 'press1';

    準備ができたところでFEDERATEDのテーブルを作るために、my5-1にwdユーザでログインし、図5のSQLを実行します。うまくで きたら参照(SELECT * FROM f_userなど)してみましょう。また、FEDERATED経由のテーブルは更新もできるので、INSERTやDELETEも試してみましょう。

    <図5:FEDERATEDエンジンのテーブルを作る>
    use wd; DROP TABLE IF EXISTS f_user; CREATE TABLE IF NOT EXISTS f_user ( id SMALLINT UNSIGNED NOT NULL ,name VARCHAR(32) NOT NULL ,age TINYINT UNSIGNED ,PRIMARY KEY (id) ) ENGINE=FEDERATED CONNECTION='mysql://wd:press2@my5-2/wd/user_master';
     

    FEDERATEDの注意点など

    とりあえずテーブルを作ってみましたが、FEDERATEDには注意点がいくつかあります。

    テーブル定義

    先の例でも見たように、参照する側(my5-1)のDDLは、参照される側(my5-2)のDDLと以下の点以外は同じにする必要があるとされています。

    • - テーブル名は異なってももよい。
    • - ENGINE=FEDERATEDにする。
    • - CONNECTION=を付加する。

    ここで図4と図5を見比べると他にも異なる点があります。そう、idカラムの「NOT NULL」です。参照される側でCREATE TABLEした際には、PRIMARY KEYによって暗黙的にNOT NULL制約が付与されましたが、(少なくとも今回の環境のバージョンでは)FEDERATEDを使う参照する側ではNOT NULLを明示的に指定する必要があり、NOT NULLを指定しない場合は

    ERROR 1121 (42000): Column ‘id’ is used with UNIQUE or INDEX but is not defined as NOT NULL

    というエラーが発生しました。

    接続ユーザのパスワード

    SHOW CREATE TABLEでFEDERATEDなテーブルのテーブル定義を見ると、接続ユーザのパスワードが表示されてしまいます。将来的に改善されることが示唆されていますが、それまでは権限情報の取り扱いには注意してください。

    トランザクション

    FEDERATED経由のテーブルに対してSTART TRANSACTIONしてもトランザクション処理は行われず、AUTOCOMMITモードの様な動作になります。同様にFEDERATED経由のテーブ ルに対してLOCK TABLESを発行しても効果はありません。

    なお、参照される側で実行されたトランザクションやLOCK TABLESは、参照する側のFEDERATEDなテーブルにも効果があります。例えば、my5-2でLOCK TABLES user_mastert WRITEが発行されると、my5-1でのSELECT * from f_userはブロックされて待たされます。

    FEDERATEDテーブルの削除

    DROP TABLE文で削除可能です。参照する側でDROP TABLEを実行しても、参照される側のテーブルは消されませんので安心してください

    参照される側のテーブル定義の変更

    参照される側のテーブル定義が変わった場合、例えばカラムが追加されたり削除された場合、参照する側のFEDERATEDなテーブルは作り直 す必要があるのですが、FEDERATEDはALTER TABLEをサポートしていないため、DROP TABLEしてCREATE TABLEしなおす必要があります。

    他のバージョンとも連携できるか

    試した限りでは、MySQL 4.0.24のテーブルを参照できました。

     

    FEDERATEDを開拓

    さて、ここまででFEDERATEDの優等生的な解説が終わりました。好奇心旺盛な読者の方はあれやこれや疑問点が出てきたのではないでしょ うか。ここからは開拓団の本領発揮ということで、あんなことやこんなことを実用性を無視して試してみたいと思います。ただ、これから紹介する挙動の中に は、もしかしたら実はバグで将来のバージョンでは修正され、挙動が変わるものもあるかもしれませんので、その点、ご了承ください。

    参照する側に作られるものは?

    参照する側でカラム定義付きのCREATE TABLE文を発行する必要があったことから推測できる通り、参照する側では何も作られないわけではなく、拡張子.frmのファイルが作られま す。*.frmファイルはFEDERATED固有のものではなくMyISAMやInnoDBのテーブルでも作られるテーブル定義が格納されているファイル です。

    自動再接続するか?

    当然、参照される側のmysqldが停止している間はFEDERATED経由で参照できませんが、参照される側が起動すれば参照する側が自動 的に再接続してくれますので、再度CREATE TABLEを発行する必要はありません。また、wait_timeout(初期値は8時間)が過ぎて接続が切れた場合も再接続してくれます。ちなみに、再 接続の機能はFEDERATEDに実装されているものではなく、FEDERATEDが接続に使用しているMySQLのCクライアント API(mysql_real_connect)の機能によるものです。

    ローカルのテーブルと結合できるか?

    できます。JOINはもちろん、UNIONなども可能です。試しにこのようなテーブルをローカルに作り、

    use wd; DROP TABLE IF EXISTS linux_user; CREATE TABLE IF NOT EXISTS linux_user ( id SMALLINT UNSIGNED ,distrib VARCHAR(32) NOT NULL ,PRIMARY KEY (id) ) ENGINE=InnoDB; INSERT INTO linux_user VALUES (1,’Debian’) ,(3,’Fedora’) ;

    FEDERATEDなf_userテーブルと結合すると、図6のように期待通り結合できることが確認できます。

    <図6:FEDERATEDテーブルとの結合>
    wd@my5-1[wd]> SELECT l.id AS id ,u.name AS name ,u.age AS age ,l.distrib AS distrib FROM linux_user AS l LEFT JOIN f_user AS u ON l.id = u.id; +----+---------+------+---------+ | id | name | age | distrib | +----+---------+------+---------+ | 1 | ichirou | 21 | Debian | | 3 | saburou | 43 | Fedora | +----+---------+------+---------+ 2 rows in set (0.01 sec)

    MySQL5開拓団 - ストレージエンジンの吟味 (1) / KLab株式会社

    他のデータベースをローカルのテーブルのように扱う為のFEDERATEDというストレージエンジンの使い方です


    1年前 | | 2010年 12月 9日 | このエントリーを含むはてなブックマーク
    2010年 12月 8日 はてなブックマーク -
    タグ: #MySQL

    今回は次のような対処を行いました。

    • max_binlog_size のデフォルト(1GB)は大きすぎるので、小さくする。
      • バイナリログがページキャッシュを何GBも使わないでほしい
      • バイナリログの読み込みは sequential read になるとはいえ、一度に1GBもreadしたくない
    • 増設対象のSlaveで実行するスクリプトを作成。疑似コードは次の通り。
    while (slaveに最新のバイナリログが転送されるまで) {
      while (バイナリログ一個分転送するまで) {
        master に ssh 経由で
          load average チェックして、高ければ下がるまで待つ
          sudo ionice -c2 -n7 cat ${binlog} > /dev/null # IOスレッドが転送するバイナリログだけを、強制的にページキャッシュに載せる
        slaveで
          START SLAVE;
          sleep
          STOP SLAVE IO_THREAD;  # バイナリログの転送より、クエリを実行する方が遥かに時間かかるので、SQLスレッドは停止しない。
          sleep                  # 他のslaveへのバイナリログ転送を阻害しないよう、 idle する
      }
      転送されたバイナリログのクエリを、SQLスレッドが実行し終わるまで待つ
    }

    # ionice は I/O scheduler が cfq じゃないと効果ないです。

    さいごに

    つまるところ、

    1. ionice cat ${binlog}
    2. バイナリログをちょっとずつ転送
    3. バイナリログ一個分転送できたら、IOスレッド止めて、転送したバイナリログのクエリを実行
    4. 1. にもどる

    を繰り返すだけのお粗末な対応ですが、これがなかなか効き目ありました。

    かつては、vmstat等でMasterの負荷を見ながら手動でSTOP SLAVE && START SLAVE を叩いて、少しずつSlave増設していたこともありましたが、いまではピークタイムのSlave増設が危なげなく行えるようになりました。また、一台の Masterに対して、複数台のSlaveを同時に追加したりもできています。

    せじまくんの刺さらない話(MySQL Slave増設編) | GREE Engineers’ Blog

    1年前 | | 2010年 12月 8日 | このエントリーを含むはてなブックマーク
    2010年 11月 27日 はてなブックマーク -
    タグ: #MySQL

    Jet Profiler for MySQL

    MySQLのチューニングが出来るプロファイラです

    1年前 | | 2010年 11月 27日 | このエントリーを含むはてなブックマーク
    2010年 11月 27日 はてなブックマーク -
    タグ: #MySQL #LVM

    LVM2 スナップショットのスナップショット機能を使った安全なファイルシステムバックアップの方法

    スナップショット先のデータは変更されないので安全にバックアップが取得できる。
    (ファイルシステムよりシステムよりのLVMの層でオリジナルデータと一緒に変更管理を行っているため、小容量で安全にできる)

    ○ 検証環境作成
    # mke2fs -j /dev/testvg/testlv1
    # mount /dev/testvg/testlv1 /mnt/test
    # echo "aaaaaa" > /mnt/test/aaaaaa
    # echo "bbbbbb" > /mnt/test/bbbbbb

    ○ lvスナップショット
    ※mysqlなどのデータベースならここでロックをかける。「FLUSH TABLES WITH READ LOCK;」
    # lvcreate -s -L 32M -n snaptestlv /dev/testvg/testlv1
    Logical volume "snaptestlv" created

    ※mysqlなどのデータベースならここでロックを解除。「UNLOCK TABLES;」

    ○ スナップショット取得検証
    オリジナルデータの変更
    # echo "cccccc" > /mnt/test/cccccc
    # echo "bbbaaa" > /mnt/test/bbbbbb

    ○ スナップショットのデータの確認
    # mkdir /mnt/snaptest
    # mount /dev/testvg/snaptestlv /mnt/snaptest
    # ls /mnt/snaptest
    aaaaa bbbbbb lost+found
    # cat /mnt/snaptest/bbbbbb
    bbbbbb
    # cat /proc/mounts
    /dev/testvg/testlv1 /mnt/test ext3 rw 0 0
    /dev/testvg/snaptestlv /mnt/snaptest ext3 rw 0 0
    # umount /mnt/snaptest
    # lvm lvdisplay testvg
    --- Logical volume ---
    LV Name /dev/testvg/snaptestlv
    VG Name testvg
    LV UUID yoJjdE-LVJE-LHN1-zrBu-u8bB-ra3R-ijQn0Z
    LV Write Access read/write
    LV snapshot status active destination for /dev/testvg/testlv1
    LV Status available
    # open 0
    LV Size 32.00 MB
    Current LE 8
    COW-table size 32.00 MB
    COW-table LE 8
    Allocated to snapshot 0.29% ←スナップショット先lvの使用率 オリジナルデータを変更するほど増加
    Snapshot chunk size 8.00 KB
    Segments 1
    Allocation inherit
    Read ahead sectors 0
    Block device 253:5

    ○ スナップショット先のファイルシステムバックアップ
    # dump 0f /tmp/testlv.dmp /dev/testvg/snaptestlv
    # lvm lvremove /dev/testvg/snaptestlv

    ※スナップショット先のlvを消さないと、いつまでもlvの古いデータコピーを続ける

    ○ バックアップデータの検証
    # rm /mnt/test/*
    # cd /mnt/test
    # restore rf /tmp/testlv.dmp
    # ls
    aaaaaa bbbbbb lost+found restoresymtable
    # cat /mnt/test/bbbbbb
    bbbbbb

    「きまぐれPCひろば」のTOPICS » (linux) LVM2 スナップショット+バックアップ

    1年前 | | 2010年 11月 27日 | このエントリーを含むはてなブックマーク