17.

MySQL「BINLOG_FORMAT = STATEMENT で binary log に書けない」の原因と対処

編集
この記事の要点
  • MySQL でレプリケーション関連の 「Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT」 エラー
  • 原因: binlog_format = STATEMENT モードで、UUID() / NOW() / RAND() 等の非決定的関数や、複数行更新を含むトランザクションを実行した
  • 基本対処A: セッション単位で ROW へ切替 SET SESSION binlog_format = 'ROW'
  • 基本対処B: my.cnf で binlog_format = ROW / MIXED に永続化
  • クエリ書き換え対処: 関数結果をアプリ側で計算してリテラル渡し、または PRIMARY KEY 指定の単純 UPDATE に変換

このエラーの概要

ERROR 1665 (HY000):
  Cannot execute statement: impossible to write to binary log since
  BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine
  limited to row-based logging.
  InnoDB is limited to row-logging when transaction isolation level is
  READ COMMITTED or READ UNCOMMITTED.

ERROR 1592 (HY000):
  Unsafe statement written to the binary log using statement format
  since BINLOG_FORMAT = STATEMENT. ...

MySQL のレプリケーションは binary log(binlog) をマスタ→スレーブへ転送して再生する仕組みです。binlog のフォーマットには 3 種類あります:

形式記録内容長所短所
STATEMENTSQL 文そのものログサイズ小非決定的関数で再現不可
ROW行の変更内容常に安全 / 正確ログサイズ大
MIXED原則 STATEMENT、危険時のみ ROWバランス判定ロジック分の負荷

このエラーは、STATEMENT モードで レプリカで結果が変わってしまう可能性のあるクエリを実行しようとして、MySQL が安全装置として止めたものです。

「危険」と判定される代表的なクエリ

  • 非決定的関数: UUID()NOW()SYSDATE()RAND()USER()CURRENT_USER()LOAD_FILE()
  • INSERT ... SELECT で AUTO_INCREMENT を含む
  • UPDATE ... LIMIT N(ORDER BY 無し)
  • READ COMMITTED 分離レベル + InnoDB の組み合わせ
  • 関数内のテーブル参照
  • システム変数 参照(@@hostname 等)

対処1: セッション単位で ROW に切替(最速)

-- 現在の設定確認
SHOW VARIABLES LIKE 'binlog_format';
-- +---------------+-----------+
-- | binlog_format | STATEMENT |
-- +---------------+-----------+

-- セッション内のみ切替(接続切れたら戻る)
SET SESSION binlog_format = 'ROW';

-- 問題のクエリを実行
UPDATE users SET token = UUID() WHERE id = 1;

-- 元に戻したい場合
SET SESSION binlog_format = 'STATEMENT';

アプリ側で実装する場合(Java/JDBC 例):

try (Connection conn = dataSource.getConnection()) {
    try (Statement stmt = conn.createStatement()) {
        stmt.execute("SET SESSION binlog_format = 'ROW'");
    }
    // この後、危険なクエリを実行
    try (PreparedStatement ps = conn.prepareStatement(
            "UPDATE users SET token = UUID() WHERE id = ?")) {
        ps.setLong(1, userId);
        ps.executeUpdate();
    }
}

対処2: グローバル設定変更(一時的)

-- 全接続に影響、ただし再起動で戻る
SET GLOBAL binlog_format = 'ROW';

-- 推奨: MIXED に
SET GLOBAL binlog_format = 'MIXED';

-- 確認
SHOW VARIABLES LIKE 'binlog_format';

-- 既存接続には反映されない。新規接続から有効

対処3: my.cnf で永続化(推奨)

# /etc/my.cnf または /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
server-id          = 1
log-bin            = /var/log/mysql/mysql-bin

# ★ 推奨: ROW (MySQL 5.7.7+ / 8.0 のデフォルト)
binlog_format      = ROW

# あるいは MIXED(GTID 利用時推奨)
# binlog_format    = MIXED

# 関連設定
binlog_row_image       = MINIMAL     # ROW 時のサイズ削減
expire_logs_days       = 7           # 古い binlog を自動削除
sync_binlog            = 1           # クラッシュ耐性
gtid_mode              = ON
enforce_gtid_consistency = ON
# 設定再読込(要再起動)
sudo systemctl restart mysqld

# 確認
mysql -e "SHOW VARIABLES LIKE 'binlog_format'"

# RDS / Aurora の場合は Parameter Group で binlog_format を変更
# → DB インスタンス再起動が必要

対処4: クエリを書き換える

binlog_format をいじれない環境(マネージド DB 等)では、クエリ側で非決定性を排除します:

-- 悪い例1: UUID() を直接埋め込む
UPDATE users SET token = UUID() WHERE id = 1;

-- 良い例1: アプリ側で生成して渡す
-- (Java) String token = UUID.randomUUID().toString();
UPDATE users SET token = '550e8400-e29b-41d4-a716-446655440000' WHERE id = 1;

-- 悪い例2: NOW() を埋め込む
INSERT INTO logs (msg, created_at) VALUES ('event', NOW());

-- 良い例2: アプリでタイムスタンプ生成
INSERT INTO logs (msg, created_at) VALUES ('event', '2026-05-17 12:34:56');

-- 悪い例3: ORDER BY なしの LIMIT
UPDATE items SET processed = 1 WHERE processed = 0 LIMIT 100;

-- 良い例3: PRIMARY KEY 指定 or ORDER BY
UPDATE items SET processed = 1
WHERE processed = 0
ORDER BY id LIMIT 100;

-- 悪い例4: INSERT ... SELECT で AUTO_INCREMENT
INSERT INTO archive (data) SELECT data FROM logs;

-- 良い例4: 明示的に PK を指定
INSERT INTO archive (id, data) SELECT id, data FROM logs;

関連: REPLICATION の確認

-- マスタ側: binlog の状態
SHOW MASTER STATUS;
-- +------------------+----------+--------------+------------------+
-- | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
-- +------------------+----------+--------------+------------------+
-- | mysql-bin.000003 |     1234 |              |                  |

-- レプリカ側: 状態確認
SHOW REPLICA STATUS\G   -- MySQL 8.0.22+
SHOW SLAVE STATUS\G    -- 旧式
-- Slave_IO_Running: Yes
-- Slave_SQL_Running: Yes
-- Seconds_Behind_Master: 0
-- Last_SQL_Error: ...   ← ここにエラーが出ていないか

-- binlog 内容のダンプ
mysqlbinlog --start-position=1234 /var/log/mysql/mysql-bin.000003

binlog_format ごとの注意点

形式レプリカ整合性ログサイズ推奨用途
STATEMENT△(非決定的関数で不一致)非推奨(5.7 でデフォルトから外れた)
ROW大(UPDATE で全カラム)推奨(5.7.7+ デフォルト)
MIXEDGTID 環境 / 互換性重視

ROW 形式のログサイズ削減

[mysqld]
binlog_format          = ROW
binlog_row_image       = MINIMAL   # 変更カラム + PK のみ記録(全カラムでなく)
binlog_row_metadata    = MINIMAL   # メタデータ最小化
binlog_rows_query_log_events = ON  # 元の SQL もコメントで残す(デバッグ用)

# 圧縮 (MySQL 8.0.20+)
binlog_transaction_compression = ON

FAQ

Q: AWS RDS で binlog_format を変えたい
A: DB Parameter Group の binlog_formatROW または MIXED に変更し、インスタンス再起動。読み取りレプリカも同じ Parameter Group を使うこと。

Q: ORM (Hibernate/Eloquent) が自動的に UUID() を発行する
A: アプリ側で生成するモードに変更してください。Hibernate なら @GeneratedValue(strategy = GenerationType.UUID)org.hibernate.id.UUIDGenerator で Java 側生成に。

Q: GTID 利用時の注意
A: GTID 有効時は enforce_gtid_consistency = ON も必須で、ROW か MIXED が事実上必須になります。STATEMENT 単独では GTID と相性が悪いです。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 1071 Specified key was too long; max key length is 767 bytes
  2. ERROR 1063 (42000): Incorrect column specifier for column '~'
  3. mysqld: Can't change dir to '...\MySQL\MySQL Server X.X\data\' (OS errno 2 - No such file or directory)
  4. Install/Remove of the Service Denied!
  5. Datetime 型が NULL に見える
  6. Warning: World-writable config file '/etc/mysql/my.cnf' is ignored
  7. ERROR 1698 (28000): Access denied for user 'root'@'localhost'
  8. Exception: Wrong MySQL configuration
  9. [Warning] TIMESTAMP with implicit DEFAULT value is deprecated.
  10. ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
  11. ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
  12. Incorrect column specifier for column 'カラム名'
  13. BLOB/TEXT column 'description' used in key specification without a key length
  14. ERROR: /bin/sh: mysql_config: コマンドが見つかりません
  15. Host '...' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'
  16. CSVエクスポート時に「ERROR 1045 (28000): Access denied for user 'username'@'localhost'」エラーが表示される
  17. Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT
  18. 1
  19. 1
  20. 1)
  21. 1
  22. 1
  23. 1
  24. 1
  25. 1
  26. 1
  27. 1
  28. 1
  29. 1
  30. 1
  31. 1
  32. 1
  33. 1"'`--
  34. 1
  35. 1
  36. 1
  37. 1
  38. 1
  39. 1
  40. 1
  41. 1
  42. 1
  43. 1)
  44. 1
  45. 1
  46. 1
  47. 1
  48. 1
  49. 1
  50. 1
  51. 1
  52. 1
  53. 1"'`--
  54. 1
  55. 1
  56. SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect string value: ~