Category Archives: データベース

[SAStruts]S2JDBCでJOINのやり方をいつも忘れるからメモ

SAStrutsでJOINを行なう際、どう書くか毎回忘れてしまう。

単純なテストを以下に記す。



■データベースのスキーマ

CREATE DATABASE db_test;
USE db_test;

CREATE TABLE channels (
	channel_id INTEGER NOT NULL AUTO_INCREMENT
	, title TEXT NOT NULL
	, PRIMARY KEY (channel_id)
);

CREATE TABLE items (
	item_id INTEGER NOT NULL AUTO_INCREMENT
	, title TEXT NOT NULL
	, url VARCHAR(255) NOT NULL
	, channel_id INTEGER NOT NULL
	, PRIMARY KEY (item_id)
	, UNIQUE (url)
);

INSERT INTO channels (title) VALUES("情報考学 Passion For The Future");
INSERT INTO channels (title) VALUES("RwJ");

INSERT INTO items (title,url,channel_id) VALUES("無趣味のすすめ","http://www.ringolab.com/note/daiya/2009/07/post-1021.html",1);
INSERT INTO items (title,url,channel_id) VALUES("アイデア・スイッチ 次々と発想を生み出す装置","http://www.ringolab.com/note/daiya/2009/07/post-1023.html",1);
INSERT INTO items (title,url,channel_id) VALUES("ベガーズ・イン・スペイン","http://www.ringolab.com/note/daiya/2009/07/post-1022.html",1);
INSERT INTO items (title,url,channel_id) VALUES("psコマンドはオプションに「-」がいらないみたい","http://blog.mikuriya.biz/archives/310",2);
INSERT INTO items (title,url,channel_id) VALUES("[SAStruts]Tomcat起動時に初回のみ呼び出す独自のクラスでデータベース接続","http://blog.mikuriya.biz/archives/171",2);
INSERT INTO items (title,url,channel_id) VALUES("[PHP]フレームワーク「CakePHP」のインストールと設定","http://blog.mikuriya.biz/archives/7",2);




■jdbc.diconを編集

今回は、MySQLを使用するので、そのあたりを編集する。

<components namespace="jdbc">
	...

	<component name="xaDataSource"
		class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
		<property name="driverClassName">
			"com.mysql.jdbc.Driver"
		</property>
		<property name="URL">
			"jdbc:mysql://localhost:3306/db_test"
		</property>
		<property name="user">"root"</property>
		<property name="password">""</property>
	</component>
	...
</components>




■channelsテーブルのクラス

package biz.mikuriya.entity;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "channels")
public class Channel {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	public int channelId;

	public String title;

	@OneToMany(mappedBy = "parent") // Itemに定義する変数名がparentになる
	public List<Item> childs;
}




■itemsテーブルのクラス

package biz.mikuriya.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "items")
public class Item {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	public int itemId;

	public String title;

	public String url;

	public int channelId;

	@ManyToOne
	@JoinColumn(name = "channel_id")
	public Channel parent; // Channel.itemsのmappedByの値と変数名を合わせる
}




■動作テスト用実行クラス

package biz.mikuriya;

import java.util.List;

import org.seasar.extension.jdbc.JdbcManager;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

import biz.mikuriya.entity.Channel;
import biz.mikuriya.entity.Item;

public class DBTest {
	private JdbcManager jdbcManager;

	/**
	 * データベース接続
	 */
	public DBTest() {
		SingletonS2ContainerFactory.init();
		S2Container container = SingletonS2ContainerFactory.getContainer();
		this.jdbcManager = (JdbcManager) container
				.getComponent(JdbcManager.class);
	}

	/**
	 * 普通にSELECT
	 */
	public void selectChannel() {
		System.out.println("CHANNELS************************************");

		List<Channel> channels = this.jdbcManager.from(Channel.class)
				.getResultList();

		for (Channel channel : channels) {
			System.out.println("\t" + channel.channelId + "\t" + channel.title);
			System.out.println("\t--------");
		}
	}

	/**
	 * 普通にSELECT
	 */
	public void selectItem() {
		System.out.println("ITEMS************************************");

		List<Item> items = this.jdbcManager.from(Item.class).getResultList();

		for (Item item : items) {
			System.out.println("\t" + item.itemId + "\t" + item.channelId
					+ "\t" + item.title + "\t" + item.url);
			System.out.println("\t--------");
		}
	}

	/**
	 * 一対多の構造で取得する場合
	 */
	public void joinOneToMany() {
		System.out.println("JOIN************************************");

		List<Channel> channels = this.jdbcManager.from(Channel.class)
				.innerJoin("childs").getResultList(); // JOINには、変数名を指定する

		for (Channel channel : channels) {
			System.out.println("\t" + channel.channelId + "\t" + channel.title);

			for (Item item : channel.childs) {
				System.out.println("\t\t" + item.itemId + "\t" + item.channelId
						+ "\t" + item.title + "\t" + item.url);
				System.out.println("\t\t--------");
			}

			System.out.println("\t--------");
		}
	}

	/**
	 * 一対一の構造で取得する場合
	 */
	public void joinOneToOne() {
		System.out.println("JOIN************************************");

		List<Item> items = this.jdbcManager.from(Item.class)
				.innerJoin("parent").getResultList(); // JOINには、変数名を指定する

		for (Item item : items) {
			System.out.println("\t" + item.itemId + "\t" + item.channelId
					+ "\t" + item.title + "\t" + item.url + "\t"
					+ item.parent.channelId + "\t" + item.parent.title);
			System.out.println("\t--------");
		}
	}

	public static void main(String[] args) {
		DBTest test = new DBTest();

		test.selectChannel();
		test.selectItem();

		test.joinOneToMany();
		test.joinOneToOne();
	}
}

MySQLではPostgreSQLでいうsetvalに似たものがない?

一応調べたんだが、MySQLにはsetvalのようなPostgreSQLの関数が無いようだ。



PostgreSQLのsetvalだが、auto_increment部分を操作するクエリだ。

なぜこんなことをMySQLで行いたいかというと、

IDがリレーションにつながった2つのテーブルをコピーしたいから。

それに加えてPostgreSQLからMySQLへの移行作業も行なう。

なおかつテーブル構造も変更している。



そんで今回とった方法がプログラムによってINSERTコピー。

まぁ、急ぎでもないし、これが一番安全かなと。



コピーが無事完了した。

auto_incrementが1のままだからコピーした分だけ

更新しなくてはならないと思ったわけ。・・・検索しても無い。



なんで?で「show table status」してみたわけ。

そしたら「auto_increment」って部分が登録値の最大になってるわけ。



で、試しにINSERTしてみたら、ちゃんと登録できたw



細かいことは調べたくないけど、まぁsetvalは必要ないみたい。

[MySQL]レプリケーションでMaster←Slave & Master←Slaveの設定に注意すること

今回、MySQLのレプリケーション設定を複数台設定することとなった。
しかし、マスター以下にスレーブを複数用意するのではない。

現在のスレーブにマスターの機能も搭載し、そいつにスレーブをくっつける設定。
以下の感じ。

(設定前)
[Master] ← [Slave]

(設定後)
[Master] ← [Slave & Master] ← [Slave]

それなりに設定作業は進み、いざ起動。
しかし、失敗。

何がいけなかったのか。

調査すると、[Slave & Master]がbin-logを出力していない。
[Slave & Master]のMySQL起動スクリプトに、あるオプションを付与しなくてはいけないようだ。

■レプリケーションスタートアップオプション
http://dev.mysql.com/doc/refman/4.1/ja/replication-options.html

--log-slave-updates

スレーブの SQL スレッドで実行された更新を、スレーブのバイナリログに記録するようにスレーブに指示する。
デフォルトではオフ。

これをつけると、マスターから取得したデータをスレーブでbin_logに出力してくれる。
これでやっと[Slave & Master]の機能を成す。

[MySQL]MERGEテーブル検証(テクニック編)

MERGEテーブルについて調査していて、新しい事実が発覚。
以下のSQLに注目。

CREATE DATABASE merge_test;
USE merge_test;

CREATE TABLE item2006 (
  id integer PRIMARY KEY AUTO_INCREMENT,
  title character varying(64) NOT NULL
);
CREATE UNIQUE INDEX u_idx_item_title ON item2006 (title);

INSERT INTO item2006 (title) VALUES('title1');
INSERT INTO item2006 (title) VALUES('title2');
INSERT INTO item2006 (title) VALUES('title3');
INSERT INTO item2006 (title) VALUES('title4');

CREATE TABLE item2007 (
  id integer PRIMARY KEY AUTO_INCREMENT,
  title character varying(64) NOT NULL
);
CREATE UNIQUE INDEX u_idx_item_title ON item2007 (title);

CREATE TABLE item (
  id integer PRIMARY KEY AUTO_INCREMENT,
  title character varying(64) NOT NULL
) TYPE=MERGE UNION=(item2006,item2007) INSERT_METHOD=LAST;
CREATE UNIQUE INDEX u_idx_item_title ON item (title);

特にMERGEテーブル作成時のUNION、INSERT_METHODに注目。

UNION=(item2006,item2007) INSERT_METHOD=LAST

SELECT時、UNIONで囲われた先頭のテーブルから検索を開始し、
結果が得られた時にSELECTが終了する。

ということは、最新のデータを取得することが多いテーブルでは、

UNION=(item2007,item2006) INSERT_METHOD=LAST

の方が効率が良い。

しかし、INSERT_METHOD=LASTが原因で、このままだとitem2006テーブルに
データが登録されてしまう。そこで、

UNION=(item2007,item2006) INSERT_METHOD=FIRST

と定義することで、UNIONの最初に定義したテーブルにINSERTを行う。

で、最終的なMERGEテーブルは、

CREATE TABLE item (
  id integer PRIMARY KEY AUTO_INCREMENT,
  title character varying(64) NOT NULL
) TYPE=MERGE UNION=(item2007,item2006) INSERT_METHOD=FIRST;
CREATE UNIQUE INDEX u_idx_item_title ON item (title);

となる。

実際にINSERT,SELECTを行なうと理解できる。

mysql> INSERT INTO item (title) VALUES('title5');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO item (title) VALUES('title6');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM item;
+----+--------+
| id | title  |
+----+--------+
|  5 | title5 |
|  6 | title6 |
|  1 | title1 |
|  2 | title2 |
|  3 | title3 |
|  4 | title4 |
+----+--------+
6 rows in set (0.00 sec)

mysql> SELECT * FROM item2007;
+----+--------+
| id | title  |
+----+--------+
|  5 | title5 |
|  6 | title6 |
+----+--------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM item2006;
+----+--------+
| id | title  |
+----+--------+
|  1 | title1 |
|  2 | title2 |
|  3 | title3 |
|  4 | title4 |
+----+--------+
4 rows in set (0.00 sec)

item2007テーブルにデータが登録されているし、
idが5,6を先にSELECTしてるっぽい。
完璧。

[MySQL]MERGEテーブル検証(実装編)

<MERGEテーブルの検証内容>

■パターン1

1.データが数件登録されたテーブルを作成。

2.同じ構造のテーブルを新しい作成する。

3.MERGEテーブルを実装


CREATE DATABASE merge_test;
USE merge_test;

CREATE TABLE item1 (
  id integer PRIMARY KEY AUTO_INCREMENT,
  title character varying(64) NOT NULL
);
CREATE UNIQUE INDEX u_idx_item_title ON item1 (title);

INSERT INTO item1 (title) VALUES('title1');
INSERT INTO item1 (title) VALUES('title2');
INSERT INTO item1 (title) VALUES('title3');
INSERT INTO item1 (title) VALUES('title4');

CREATE TABLE item2 (
  id integer PRIMARY KEY AUTO_INCREMENT,
  title character varying(64) NOT NULL
);
CREATE UNIQUE INDEX u_idx_item_title ON item2 (title);

CREATE TABLE item (
  id integer PRIMARY KEY AUTO_INCREMENT,
  title character varying(64) NOT NULL
) TYPE=MERGE UNION=(item1,item2) INSERT_METHOD=LAST;
CREATE UNIQUE INDEX u_idx_item_title ON item (title);



MERGEテーブルに検索すると、結果はこんな感じ

mysql> SELECT * FROM item;
+----+--------+
| id | title  |
+----+--------+
|  1 | title1 |
|  2 | title2 |
|  3 | title3 |
|  4 | title4 |
+----+--------+
4 rows in set (0.00 sec)





○パターン1?1

どこにINSERTを行なえばよいのか?

ひとまずMERGEテーブルにINSERTを行なってみる。

mysql> INSERT INTO item (title) VALUES('title5');
Query OK, 1 row affected (0.00 sec)

mysql> select * from item1;
+----+--------+
| id | title  |
+----+--------+
|  1 | title1 |
|  2 | title2 |
|  3 | title3 |
|  4 | title4 |
+----+--------+
4 rows in set (0.00 sec)

mysql> select * from item2;
+----+--------+
| id | title  |
+----+--------+
|  5 | title5 |
+----+--------+
1 row in set (0.00 sec)



ん!?入っちゃった。

AUTO_INCREMENTも問題なし。

しかも、なぜitem2テーブルにデータが入る?

後に作ったテーブルに優先してデータが入るのか?



MySQLのマニュアルを見ると、

「MERGE テーブルへの挿入が UNIONリスト内の最初のテーブルと

最後のテーブルのどちらで行われるかを、INSERT_METHODで指定できます。」

と書いてある。



従って今回の場合は、UNIONの最後がitem2であり、かつ「INSERT_METHOD=LAST」だったので、

item2テーブルにデータがINSERTされたようだ。

http://dev.mysql.com/doc/refman/4.1/ja/merge.html



これでAUTO_INCREMENTも継続して使用できるので、安心だ。





○パターン1?2

今度は、MERGEテーブルのデータの整合性について検証してみる。



一度、データベースを削除して最初から検証開始。

パターン1で記述したSQLで再度データベース作成。


INSERT INTO item (title) VALUES('title5');
INSERT INTO item (title) VALUES('title5');

2度同じSQLを実行してみた。

問題なくエラー発生したので、次の検証へ。


INSERT INTO item2 (title) VALUES('title6');
INSERT INTO item (title) VALUES('title6');

わざとitem2テーブルにデータを登録し、その後itemテーブルにINSERTを行なう。

問題なくエラーが発生したので、次の検証へ。


INSERT INTO item1 (title) VALUES('title7');
INSERT INTO item (title) VALUES('title7');

わざとitem1テーブルにデータを登録し、その後itemテーブルにINSERTを行なう。

エラーが発生しないで、登録されてしまった!

まぁ、当たり前だが、SELECTではどうか。

mysql> SELECT * FROM item;
+----+--------+
| id | title  |
+----+--------+
|  1 | title1 |
|  2 | title2 |
|  3 | title3 |
|  4 | title4 |
|  5 | title7 |
|  5 | title5 |
|  6 | title6 |
|  7 | title7 |
+----+--------+
8 rows in set (0.00 sec)

IDが重複して取得できてしまっている。



○パターン1?3

DELETEやUPDATEはどうなるのか検証してみる。



一度、データベースを削除して最初から検証開始。

パターン1で記述したSQLで再度データベース作成。

itemテーブルに1レコード追加。

INSERT INTO item (title) VALUES('title5');



item1テーブルに存在するレコードをDELETEしてみる。

mysql> DELETE FROM item WHERE title = 'title1';
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM item;
+----+--------+
| id | title  |
+----+--------+
|  2 | title2 |
|  3 | title3 |
|  4 | title4 |
|  5 | title5 |
+----+--------+
4 rows in set (0.00 sec)

消えた!

次にid条件でもDELETEしてみる。

mysql> DELETE FROM item WHERE id = 3;
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM item;
+----+--------+
| id | title  |
+----+--------+
|  2 | title2 |
|  4 | title4 |
|  5 | title5 |
+----+--------+
3 rows in set (0.00 sec)

消えた!

次にid条件で2つのテーブルをまたいでみる。

mysql> DELETE FROM item WHERE id >= 4;
Query OK, 2 rows affected (0.00 sec)

mysql> SELECT * FROM item;
+----+--------+
| id | title  |
+----+--------+
|  2 | title2 |
+----+--------+
1 row in set (0.00 sec)

消えた!

2つのテーブルをまたいでも、問題なく消えた!



今度は、UPDATEを検証したいが・・・

まぁ検証しなくても大丈夫でしょ。





<検証した感想>

超普通のことだが、感想を述べる。

・「MERGEテーブルを実装したら、SELECT以外は、MERGEテーブルに送るべき」

以上!

[MySQL]ストレージエンジン「MERGEテーブル」ってなんだ?ちょっとだけ興味を持った。

MERGEテーブルは、同じ構造のテーブルを何個も作って、

仮想的にそれらのテーブルを一つに見せるストレージエンジンだ。



大容量のデータを扱う時に有効かもしれないので、MERGEテーブルを調査してみた。

実装、パフォーマンスチェックは全然行っていないので、あくまでも予想。



■分かったこと(予想もあり)

・I/Oを分散できるかもしれない。(いや出来る)

・INSERTを行なわないテーブルをmyisampackで圧縮しても扱うことが出来る。

・MERGEテーブルの作成に大した時間がかからないっぽい。

・auto_incrementが次のテーブルに継承される。

・naoya氏のブログに書いてあったのだが、Seesaaブログは月ごとにテーブルを作成しているらしい。

 月単位の検索は、直接指定した月のテーブルに検索をかける。その他は、MERGEテーブルだそうな。



■分からないこと、知りたいこと

別サーバ間でMERGEテーブルは、適用できるのか?

・バージョン4.1.1以降?から別のデータベースでアクセスできるっぽい。



■絶対買うべき本



実践ハイパフォーマンスMySQL



■参考URL

テーブルがMERGEの場合はどうなるか

MySQL/最適化/テーブル

MySQL の MERGE テーブルで巨大なテーブルを効率的に運用する





うちのDBもあと数ヶ月で2テラを埋め尽くしてしまう。

解決策となってくれればよいが・・・。

HP World Tokyo 2007でmixiのMySQL Clusterについて聞いてきた





本日、六本木のミッドタウンでHP主催の

「HP World Tokyo 2007」が開催されました。





船木氏と一緒にmixi社員が語る「事例研究 MySQL Cluster動作検証と適合ポイントを探る」

というプレゼンを聞いてきました。



大体の内容を超簡潔にまとめました。



■なぜプレゼンやったのか?を予想してみた

mixiの新入社員(訂正)が、「やることないのでMySQL Clusterを検証してみた」的なノリで

プレゼンを行なったっぽいです。

あくまでも予想なので気にしないで下さい。



■mixiの問題点

 負荷対策

 データ量の増加

 Master更新の負荷

 データの複雑化



■テスト環境

HPから12台のサーバを提供してもらい、それでテストした。

mixiは本来Fedora Coreで運用しているが、今回はRedHatでテスト。



○MySQL5.0系(SQL Node)

 メモリ:8GB

 CPU:Xeon 3.0Ghz



○NDB(Data Node)

 メモリ:16GB

 CPU:Xeon 3.0Ghz



■検証したこと

○MySQL Clusterの動作検証を行なう。

○パフォーマンステスト、耐久テストは、今回行なわない。

○少量のデータを使用してテストを行なった(mixiの日記データに近い構造)。

○以下のURLとほとんど同様の環境を構築する。

 http://itpro.nikkeibp.co.jp/article/COLUMN/20060715/243478/



何パターンかサーバ構成を変更して検証を行なっていたが、

結局知りたいことは、以下の項目。

・データの整合性

・障害時のデータ状態

・バックアップ

・その他、至って普通の検証内容



■検証して分かったこと

 ・データが壊れても問題がないようなデータベースには、オススメ

 ・キャッシュ的な機能として利用すると良いかも

 ・セッションを保持するためのデータベースとして活用できそう

 ・環境構築が楽

 ・Data Nodeを後から増やせない

 ・Data Nodeが一つ壊れると、一度停止しなくてはならない?



■結論

1.壊れてもよいデータベースやテーブルには、

  最適であると言えるが、壊れてもよいデータって存在するのか・・・。



2.セッションを保持するデータベースとしての活用は良さそうな気はするが、

  うちのような会社には必要のないと思う。



■その他情報

mixiで使用しているサーバは現在1000台ほどで、

WEBアプリケーションサーバとデータベースサーバで半々に分かれる。

[MySQL]レプリケーションでスレーブ(Slave)エラーの対処(データの整合性)

以前の「[データベース]MySQL-5.0.27のレプリケーション設定」によって、
レプリケーション設定が完了した。

次は、予期せぬレプリケーションエラーに対処できるかどうかを調査・テストする。
テストの流れは、こんな感じ。
・テスト用のデータベースを作成
・わざとスレーブ(Slave)側にレコードを登録
・マスターにレコードを追加し、エラーを確認
・スレーブが本当に止まっているかテスト
・MySQLを止めずにデータの整合性をとる

■テスト用のデータベースを作成
まず、通常通りにテストデータベースを作成する。
この時、すでにレプリケーション設定が完了されているものとする。

 # mysql -u root
 mysql> CREATE DATABASE testdb;
 mysql> use testdb
 mysql> CREATE TABLE item_data (item_no integer PRIMARY KEY AUTO_INCREMENT,
      -> title character varying(255));
 mysql> INSERT INTO item_data (title) VALUES('Test1');
 mysql> INSERT INTO item_data (title) VALUES('Test2');
 mysql> INSERT INTO item_data (title) VALUES('Test3');

Master、Slaveにてデータの登録確認を行なう。

 mysql> use testdb;
 Database changed
 mysql> select * from item_data;
 +---------+-------+
 | item_no | title |
 +---------+-------+
 |       1 | Test1 |
 |       2 | Test2 |
 |       3 | Test3 |
 +---------+-------+
 3 rows in set (0.00 sec)

両データベースでデータの整合性を確認できなかったら、
「[データベース]MySQL-5.0.27のレプリケーション設定」に戻って設定しなおし。

■わざとスレーブ(Slave)側にレコードを登録
スレーブにレコードを追加することで、データの整合性がとれなくなる。
すると、エラーを起こすはず。

 Slave側にレコード追加
 mysql> INSERT INTO item_data (title) VALUES('Test4');

Slaveにレコードが登録されているか確認。

 mysql> select * from item_data;
 +---------+-------+
 | item_no | title |
 +---------+-------+
 |       1 | Test1 |
 |       2 | Test2 |
 |       3 | Test3 |
 |       4 | Test4 |
 +---------+-------+
 4 rows in set (0.00 sec)

Master側には、「Test4」が登録されていないはず。

■マスターにレコードを追加し、エラーを確認
データの整合性がとれていないため、Masterにレコードを追加すればSlave側で、
エラーが発生するはず。

Masterにレコードを追加する。

 mysql> INSERT INTO item_data (title) VALUES('Test5');

Masterにレコードが追加されたか確認する。

 mysql> select * from item_data;
 +---------+-------+
 | item_no | title |
 +---------+-------+
 |       1 | Test1 |
 |       2 | Test2 |
 |       3 | Test3 |
 |       4 | Test5 |
 +---------+-------+
 4 rows in set (0.00 sec)

Slaveには、item_no=4のレコードがすでに登録されているので、
Slave側でエラーが発生しているはず。
エラーが起きていることを確認してみる。

 Slave側のエラーを確認
 mysql> show slave status \G;
 *************************** 1. row ***************************
              Slave_IO_State: Waiting for master to send event
                 Master_Host: 192.168.0.3
                 Master_User: hoge
                 Master_Port: 3306
               Connect_Retry: 60
             Master_Log_File: mysql-bin.000023
         Read_Master_Log_Pos: 1371
              Relay_Log_File: hoge-relay-bin.000015
               Relay_Log_Pos: 1370
       Relay_Master_Log_File: mysql-bin.000023
            Slave_IO_Running: Yes
           Slave_SQL_Running: No
             Replicate_Do_DB:
         Replicate_Ignore_DB:
          Replicate_Do_Table:
      Replicate_Ignore_Table:
     Replicate_Wild_Do_Table:
 Replicate_Wild_Ignore_Table:
                  Last_Errno: 1062
                  Last_Error: Error 'Duplicate entry '4' for key 1' on query. Default database: 'testdb'.
                              Query: 'INSERT INTO item_data (title) VALUES('Test5')'
                Skip_Counter: 0
         Exec_Master_Log_Pos: 1233
             Relay_Log_Space: 1508
             Until_Condition: None
              Until_Log_File:
               Until_Log_Pos: 0
          Master_SSL_Allowed: No
          Master_SSL_CA_File:
          Master_SSL_CA_Path:
             Master_SSL_Cert:
           Master_SSL_Cipher:
              Master_SSL_Key:
       Seconds_Behind_Master: NULL
 1 row in set (0.00 sec)

・Slave_SQL_Running
通常は、「Yes」になっているが、エラーが発生したことで「No」になっている。
・Last_Error
エラーの理由が表示されている。
item_no=4は、すでに登録済みである、と。

MySQLのログにも出力されている。

 [mysql@hoge data]# cd {$MYSQL_HOME}/data/
 [mysql@hoge data]# tail -n 3 hoge.err
 070517 17:26:58 [ERROR] Slave: Error 'Duplicate entry '4' for key 1' on query. Default database: 'testdb'.
                         Query: 'INSERT INTO item_data (title) VALUES('Test5')', Error_code: 1062
 070517 17:26:58 [ERROR] Error running query, slave SQL thread aborted.
                         Fix the problem, and restart the slave SQL thread with "SLAVE START".
                         We stopped at log 'mysql-bin.000023' position 1233

Slaveは止まっているようだ。
エラーを修復し、Slaveを再度起動する必要がある。

■スレーブが本当に止まっているかテスト
Slaveが止まっているならば、Masterにレコードを追加してもSlaveには反映されないはず。

 Masterにレコード追加
 mysql> INSERT INTO item_data (title) VALUES('Test6');
 mysql> select * from item_data;
 +---------+-------+
 | item_no | title |
 +---------+-------+
 |       1 | Test1 |
 |       2 | Test2 |
 |       3 | Test3 |
 |       4 | Test5 |
 |       5 | Test6 |
 +---------+-------+
 5 rows in set (0.00 sec)

Slaveにレコードが追加されていないことを確認する。

 mysql> select * from item_data;
 +---------+-------+
 | item_no | title |
 +---------+-------+
 |       1 | Test1 |
 |       2 | Test2 |
 |       3 | Test3 |
 |       4 | Test4 |
 +---------+-------+
 4 rows in set (0.00 sec)

■MySQLを止めずにデータの整合性をとる
Masterに登録されているitem_no=4を飛ばして、再度Slaveを起動させる。

 mysql> SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
 mysql> START SLAVE SQL_THREAD;

Slaveに先ほどMasterに追加したitem_no=5のデータのみが反映されているはず。

 mysql> select * from item_data;
 +---------+-------+
 | item_no | title |
 +---------+-------+
 |       1 | Test1 |
 |       2 | Test2 |
 |       3 | Test3 |
 |       4 | Test4 |
 |       5 | Test6 |
 +---------+-------+
 5 rows in set (0.00 sec)

上記の情報を見る限り、Masterのitem_no=4、title=Test5がスキップされていることが分かる。

解説するのが面倒なので書かないが、
AUTO_INCREMENTによるitem_noのズレが発生するのかと心配していたが、
Slaveをどんなに変更しても、Masterのitem_noは、引き継がれる。
間違いない。

■参考URL
http://www.irori.org/doc/mysql-rep.html
http://itpro.nikkeibp.co.jp/members/ITPro/oss/20050712/164383/?ST=oss&P=1

[MySQL]レプリケーション設定時のバイナリログ(bin-log)のエラーとその対処

MySQLのバージョンは、「5.0.27」。
MySQLのレプリケーション設定を行なった際のエラーとその対処について解説する。

■起きた現象
レプリケーション設定をそのまま行なったが、以下のエラーが発生。

070410 15:19:48 [ERROR] Slave: Error 'Table 'other_databasename.other_tablename' doesn't exist' on query.
                        Default database: 'other_databasename'.
                        Query: 'INSERT INTO other_tablename (id,title,link) VALUES(11111,'TITLE');

070410 15:19:48 [ERROR] Error running query, slave SQL thread aborted. Fix the problem,
                        and restart the slave SQL thread with "SLAVE START".
                        We stopped at log 'mysql-bin.000003' position 1234

「other_databasename」なんていうデータベースは無いぞ、という意味。

■原因
「other_databasename」は、レプリケーション対象ではなく、全く関係ないデータベースであったが、
スレーブ(SLAVE)側でレプリケーションするデータベースを選択していなかったため、
レプリケーションの対象が全てのデータベースとなってしまっていた。
間違って別のデータベースのレプリケーションまで行なおうとしてエラーが発生。

■解決策1(マスター側)
マスター(Master)側で解決するには、マスターのbin-log出力をレプリケーション対象のデータベースのみに
設定する必要がある。設定は、my.cnfへ記述する。

binlog-do-db=データベース名

設定を行い再起動すると、指定したデータベースのみbig-logに出力される。
この場合、「RESET SLAVE」、「CHANGE MASTER」などで再度レプリケーション設定を行なわなくてはならない。

■解決策2(スレーブ側)
スレーブ(Slave)側では、細かい設定まで行なうことが出来る。
http://dev.mysql.com/doc/refman/4.1/ja/replication-options.html

[MySQL]FLUSH TABLESについて

レプリケーション設定を行なう際にデータをコピーしようとした。
SELECTやINSERT、全てのクエリを行なわない状態であるのに、
1分おき位にデータファイル(MYIファイル)が更新されている・・・。
しかし、サイズは変わらない。

データの不整合がでてほしくないので、ひとまずFLUSH TABLEを行なった。

mysql> FLUSH TABLE mytable_name;

小さいテーブルで行なったので、1秒かからずに終了。

次に3000万件ほどのレコードを登録しているテーブルを
FLUSH TABLE。これも1秒かからずに終了。

というわけで、FLUSH TABLESを行なうことにした。
リファレンスには、このような説明がある。

開いているテーブルをすべて閉じる。
使用中のテーブルも強制的に閉じる。
これにより、クエリキャッシュもフラッシュされる。

http://dev.mysql.com/doc/refman/4.1/ja/flush.html
FLUSH TABLESなので、コマンドの名前からしてデータベース単位で、
「テーブルを閉じる」のかと思っていたが、いざ実行してみると、
他のデータベースもテーブルを閉じ始めた。

結局、数分後に終わったから良かったものの、
全てのデータベースを閉じるとは、思ってもみなかった。

MYIファイルの更新もされなくなり、正常にデータをコピーすることが出来た。