GA

2023/02/12

ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed には実は2種類ある

TL;DR

  • SET GLOBAL gtid_purged = '+?' のプラス付きの記法は、既存の gtid_executed と1つたりともカブってはいけない。
    • RESET MASTER ができず、既存の gtid_executed に足したい場合は差分を取って + 記号で足してやらないといけない
  • gtid_executed が空でない時に + 記号なしの SET GLOBAL gtid_purged = ? した時のエラーが5.7と8.0で変わってた

SET GLOBAL gtid_purged = ? した時に出ることがある ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed, よく見たら実は3種類くらいあるっぽかった。

perror で調べてみると、確かに ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: %s なので cannot be changed: までが固定のメッセージで、その理由が後ろに添えられるっぽい。

$ perror 3546
MySQL error code MY-003546 (ER_CANT_SET_GTID_PURGED_DUE_SETS_CONSTRAINTS): @@GLOBAL.GTID_PURGED cannot be changed: %s

my_error (MySQLがエラーパケットを返す時に使われる関数) で ER_CANT_SET_GTID_PURGED_DUE_SETS_CONSTRAINTS を渡しているのはMySQL 8.0.32のコードで3か所。

https://github.com/mysql/mysql-server/blob/mysql-8.0.32/sql/rpl_gtid_state.cc#L622-L646

+ 記号がついてない時は簡単, if文で1つ目と3つ目に当たる。

mysql80 7> SELECT @@gtid_executed;
+----------------------------------------------+
| @@gtid_executed                              |
+----------------------------------------------+
| dddc3a2c-96fe-11ed-ae6d-0201965f8d32:1-53562 |
+----------------------------------------------+
1 row in set (0.01 sec)

mysql80 7> SET GLOBAL gtid_purged = 'dddc3a2c-96fe-11ed-ae6d-0201965f8d32:1';
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the new value must be a superset of the old value

mysql80 7> SET GLOBAL gtid_purged = 'dddc3a2c-96fe-11ed-ae6d-0201965f8d32:1-53562';
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the added gtid set must not overlap with @@GLOBAL.GTID_EXECUTED

mysql80 8> SET GLOBAL gtid_purged = 'dddc3a2c-96fe-11ed-ae6d-0201965f8d32:53563';
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the new value must be a superset of the old value

mysql80 8> SET GLOBAL gtid_purged = 'dddc3a2c-96fe-11ed-ae6d-0201965f8d31:1';  -- server_uuid部分が違ってもダメ
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the new value must be a superset of the old value

これは5.7とそれ以前の ERROR 1840 (HY000): @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty. に対応していて、「既存の gtid_executed よりも小さく、かつ大きくなければならない」と言っているのでどう考えても無理。 RESET MASTER して新たに設定するしかないはず。

ちなみに5.7とそれ以前の MySQL error code MY-001840 (ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY): @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty. は8.0ではどこからも呼び出されていない。

+ を付けた時でもエラることはある。エラーメッセージを素直に読むと、既存の部分とカブってはいけないらしい。

mysql80 8> SELECT @@gtid_executed;
+----------------------------------------------+
| @@gtid_executed                              |
+----------------------------------------------+
| dddc3a2c-96fe-11ed-ae6d-0201965f8d32:1-53562 |
+----------------------------------------------+
1 row in set (0.00 sec)

mysql80 8> SET GLOBAL gtid_purged = '+dddc3a2c-96fe-11ed-ae6d-0201965f8d32:1-53563';
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the added gtid set must not overlap with @@GLOBAL.GTID_EXECUTED

既存の部分とカブらせないようにした↓は通る。

mysql80 8> SET GLOBAL gtid_purged = '+dddc3a2c-96fe-11ed-ae6d-0201965f8d32:53563';
Query OK, 0 rows affected (0.06 sec)

mysql80 8> SELECT @@gtid_executed;
+----------------------------------------------+
| @@gtid_executed                              |
+----------------------------------------------+
| dddc3a2c-96fe-11ed-ae6d-0201965f8d32:1-53563 |
+----------------------------------------------+
1 row in set (0.00 sec)

mysql80 8> SET GLOBAL gtid_purged = '+dddc3a2c-96fe-11ed-ae6d-0201965f8d31:1';  -- server_uuid部分を変えたものは別gtidなのでもちろん通る
Query OK, 0 rows affected (0.01 sec)

mysql80 8> SELECT @@gtid_executed;
+--------------------------------------------------------------------------------------+
| @@gtid_executed                                                                      |
+--------------------------------------------------------------------------------------+
| dddc3a2c-96fe-11ed-ae6d-0201965f8d31:1,
dddc3a2c-96fe-11ed-ae6d-0201965f8d32:1-53563 |
+--------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

これに当たるのはたぶん、

  • 「レプリカがしばらく止まっててI/Oスレッドが 'Cannot replicate because the master purged required binary logs. Replicate the missing transactions from elsewhere, or provision a new slave from backup. Consider increasing the master's binary log expiration period. The GTID set sent by the slave is '', and the missing transactions are 'xxx:yyy で再開できなくなったから、インスタンスはそのままにmysqldumpで上書きして再開させてやろう」と思った時
  • マルチソースレプリケーションを組んでて↑になった時
    のパターンだと思う。

前者はインスタンスをつぶしてきれいに再構築するか、 RESET MASTER してから通せば良いけれど、後者の時は差分を取って綺麗に + で当ててやらないといけないので、mysqldumpに書いてある SET GLOBAL gtid_purged=? の部分から

SET GLOBAL gtid_purged= CONCAT('+', GTID_SUBTRACT(/* GTID_SUBTRACTの1つ目の引数がmysqldumpから確認したgtid_purgedにセットしたい値 */ 'dddc3a2c-96fe-11ed-ae6d-0201965f8d31:1,dddc3a2c-96fe-11ed-ae6d-0201965f8d32:1-53563', /* 第2引数をMySQLが認識しているgtid_executedにすると差分がとれる */ @@gtid_executed));

と、差分だけ追加する形でgtid_purgedをセットできた。

しかし、 + 記号がついてない時のエラーは MySQL error code MY-001840 (ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY): @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty. で良かったんじゃないかなと思う。

0 件のコメント :

コメントを投稿