MySQL 5.1.44リリース

出てます。今回は機能追加が1件、バグ修正が18件あります。バグ修正のうちパーティショニングに関するものが1件、レプリケーションに関するものが5件となっています。InnoDB PluginはMySQL 5.1.42、5.1.43から引き続きRC版の1.0.6となっています。
MySQL 5.1.44では設定パラメータが一つ追加されました。

  • binlog-direct-non-transactional-updates

これはMyISAMなどトランザクションに対応していないテーブルに対する更新を、即座にバイナリログに書き出すパラメータです。これでいったい何が変わるのか、以下の例で確認してみましょう。

mysql> select * from test_i;
+----+------+
| id | data |
+----+------+
|  1 |   20 |
|  2 |   40 |
+----+------+
2 rows in set (0.00 sec)

mysql> select * from test_m;
+-----+------+
| id  | data |
+-----+------+
| 101 |  100 |
+-----+------+
1 row in set (0.00 sec)

test_iはトランザクションをサポートしたInnoDBのテーブル、test_mはトランザクションに対応していないMyISAMのテーブルです。これらのテーブルに対し、MySQL 5.1.43までのバージョンで以下の処理を実行すると…

client1> begin;
client1> update test_i set data = data + 1 where id = 1;
client1> update test_m set data = 110 where id = 101;
client2> begin;
client2> update test_i set data = data + 1 where id = 2;
client2> update test_m set data = 120 where id = 101;
client2> commit;
client1> commit;

test_mテーブルの内容は以下のようになります。

mysql> select * from test_m;
+-----+------+
| id  | data |
+-----+------+
| 101 |  120 |
+-----+------+
1 row in set (0.00 sec)

ところが、バイナリログの中身を見てみると…

# at 1130
#100228 20:19:19 server id 1  end_log_pos 1199 	Query	thread_id=2	exec_time=0	error_code=0
SET TIMESTAMP=1267355959/*!*/;
BEGIN
/*!*/;
# at 1199
#100228 20:18:04 server id 1  end_log_pos 1309 	Query	thread_id=2	exec_time=0	error_code=0
SET TIMESTAMP=1267355884/*!*/;
update test_i set data = data + 1 where id = 2
/*!*/;
# at 1309
#100228 20:18:54 server id 1  end_log_pos 1416 	Query	thread_id=2	exec_time=0	error_code=0
SET TIMESTAMP=1267355934/*!*/;
update test_m set data = 120 where id = 101
/*!*/;
# at 1416
#100228 20:19:19 server id 1  end_log_pos 1443 	Xid = 19
COMMIT/*!*/;
# at 1443
#100228 20:19:31 server id 1  end_log_pos 1512 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1267355971/*!*/;
BEGIN
/*!*/;
# at 1512
#100228 20:17:47 server id 1  end_log_pos 1622 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1267355867/*!*/;
update test_i set data = data + 1 where id = 1
/*!*/;
# at 1622
#100228 20:18:44 server id 1  end_log_pos 1729 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1267355924/*!*/;
update test_m set data = 110 where id = 101
/*!*/;
# at 1729
#100228 20:19:31 server id 1  end_log_pos 1756 	Xid = 18
COMMIT/*!*/;

このように、最終的にtest_mテーブルのdata列の値が110になってしまっています。これはつまりレプリケーションでデータ不整合が起きるとか、バックアップデータをリストア・リカバリしたら元のデータと違っているとかいう話ですから、非常にまずい状況です。どうしてこのようなことが起こるかというと、MySQLの作りとして、トランザクションの中で行った更新はCOMMITして初めてバイナリログに書き出されるようになっているためです。
MySQL 5.1.44でbinlog-direct-non-transactional-updatesを有効にすると、バイナリログが以下のように変わります。

# at 298
#100308  2:01:25 server id 1  end_log_pos 405 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1267981285/*!*/;
update test_m set data = 110 where id = 101
/*!*/;
# at 405
#100308  2:01:38 server id 1  end_log_pos 512 	Query	thread_id=2	exec_time=0	error_code=0
SET TIMESTAMP=1267981298/*!*/;
update test_m set data = 120 where id = 101
/*!*/;
# at 512
#100308  2:01:41 server id 1  end_log_pos 581 	Query	thread_id=2	exec_time=0	error_code=0
SET TIMESTAMP=1267981301/*!*/;
BEGIN
/*!*/;
# at 581
#100308  2:01:34 server id 1  end_log_pos 691 	Query	thread_id=2	exec_time=0	error_code=0
SET TIMESTAMP=1267981294/*!*/;
update test_i set data = data + 1 where id = 2
/*!*/;
# at 691
#100308  2:01:41 server id 1  end_log_pos 718 	Xid = 6
COMMIT/*!*/;
# at 718
#100308  2:01:43 server id 1  end_log_pos 787 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1267981303/*!*/;
BEGIN
/*!*/;
# at 787
#100308  2:01:17 server id 1  end_log_pos 897 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1267981277/*!*/;
update test_i set data = data + 1 where id = 1
/*!*/;
# at 897
#100308  2:01:43 server id 1  end_log_pos 924 	Xid = 3
COMMIT/*!*/;

無事、data列の値が120になりました。
ではbinlog-direct-non-transactional-updatesは常に有効にすべきパラメータかというと、そうではありません。このパラメータには副作用があって、例えば以下のようなMyISAMInnoDBを混ぜて利用するSQLでは逆に不整合を起こしてしまいます。

mysql> begin;
mysql> update test_i set data = 25 where id = 1;
mysql> update test_m set data = (select data from test_i where id = 1);
mysql> commit;

mysql> select * from test_m;
 +-----+------+
 | id  | data |
 +-----+------+
 | 101 |   25 |
 +-----+------+
1 row in set (0.00 sec)
# at 924
#100308  2:10:43 server id 1  end_log_pos 1051 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1267981843/*!*/;
update test_m set data = (select data from test_i where id = 1);
/*!*/;
# at 1051
#100308  2:10:48 server id 1  end_log_pos 1120 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1267981848/*!*/;
BEGIN
/*!*/;
# at 1120
#100308  2:10:21 server id 1  end_log_pos 1224 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1267981821/*!*/;
update test_i set data = 25 where id = 1
/*!*/;
# at 1224
#100308  2:10:48 server id 1  end_log_pos 1251 	Xid = 11
COMMIT/*!*/;

さらに、このパラメータはMySQL 5.1で行ベースのバイナリログを使用している場合は効果がありません。このことから今回の機能追加はあくまで暫定対処だということが分かると思います。アプリケーション開発者の方は以下の流れに沿ってこのパラメータの使用有無を検討してください。

  1. 基本的に1つのトランザクションの中でMyISAMInnoDBの両方を更新しないこと
  2. どうしても更新したい場合は、先にMyISAMを更新すること (MySQL 5.1.39リリース - SH2の日記を参照)
  3. それでもどうしてもMyISAMの更新が後になる場合は、MyISAMInnoDBを混ぜて利用するSQLが無いことを確認した上でbinlog-direct-non-transactional-updatesを有効にすること

MySQLの次のメジャーバージョンでは、行ベースのバイナリログを使用した場合に限りこの問題が解決される見込みです。詳しくはWL#2687: Write non-transactional binlog events directly to binary logをご参照ください。