Master/Slave関係にあるMySQLサーバでMasterに無いテーブルをSlaveに作成した時の挙動
モチベーション
Masterに存在しないテーブルをSlaveで作成した時の挙動とさらに同じテーブルをMasterで作成した時の挙動を検証ベースで確認したい。
Masterに存在しないテーブルをSlaveで作成する
レプリケーションの基本的な考え方として、Slave側から更新をしてはならないというものがある。実際には、レプリケーションが止まるだけなのでDBとしては利用できるのだが、再びレプリケーションを開始するには色々とDBの状態を同期する必要がある。一方で、Masterに存在しないテーブルをSlaveで作成しても実はレプリケーションは止まらないことを知った。実際に確認してみる。
@ Master mysql> show master status; +------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000003 | 107 | | | +------------------+----------+--------------+------------------+ mysql> use repl_test; mysql> show tables; Empty set (0.00 sec)
@ Slave mysql> show slave status \G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.33.10 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000003 Read_Master_Log_Pos: 107 Relay_Log_File: mysqld-relay-bin.000006 Relay_Log_Pos: 253 Relay_Master_Log_File: mysql-bin.000003 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 107 Relay_Log_Space: 556 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: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1
この状態でSlaveの方に新しいテーブルを作成すると、レプリケーションが続いていることがわかる。
@ Slave mysql> use repl_test; Database changed mysql> CREATE TABLE emp ( -> emp_id INTEGER UNSIGNED AUTO_INCREMENT, -> emp_name VARCHAR(50) NOT NULL, -> emp_roman VARCHAR(50) NOT NULL, -> emp_email VARCHAR(100) NOT NULL, -> INDEX(emp_roman), -> PRIMARY KEY (emp_id) -> ); Query OK, 0 rows affected (0.01 sec) mysql> show tables; +---------------------+ | Tables_in_repl_test | +---------------------+ | emp | +---------------------+ mysql> show slave status \G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.33.10 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000003 Read_Master_Log_Pos: 200 Relay_Log_File: mysqld-relay-bin.000006 Relay_Log_Pos: 346 Relay_Master_Log_File: mysql-bin.000003 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 200 Relay_Log_Space: 649 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: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1
なるほど。
同じテーブルをMasterで作成してみる
現状はSlaveにしかempテーブルが無い状態だが、Masterで同じクエリを実行したらどうなるだろうか。
@ Master mysql> use repl_test; Database changed mysql> CREATE TABLE emp ( -> emp_id INTEGER UNSIGNED AUTO_INCREMENT, -> emp_name VARCHAR(50) NOT NULL, -> emp_roman VARCHAR(50) NOT NULL, -> emp_email VARCHAR(100) NOT NULL, -> INDEX(emp_roman), -> PRIMARY KEY (emp_id) -> ); Query OK, 0 rows affected (0.01 sec) mysql> show tables; +---------------------+ | Tables_in_repl_test | +---------------------+ | emp | +---------------------+
Masterではテーブルが作られているが、すでに同じテーブルが存在するSlaveの方でレプリケーション状態を確認すると、当然ながらエラーが出ている。
@ Slave mysql> show slave status \G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.33.10 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000003 Read_Master_Log_Pos: 475 Relay_Log_File: mysqld-relay-bin.000006 Relay_Log_Pos: 346 Relay_Master_Log_File: mysql-bin.000003 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: 1050 Last_Error: Error 'Table 'emp' already exists' on query. Default database: 'repl_test'. Query: 'CREATE TABLE emp ( emp_id INTEGER UNSIGNED AUTO_INCREMENT, emp_name VARCHAR(50) NOT NULL, emp_roman VARCHAR(50) NOT NULL, emp_email VARCHAR(100) NOT NULL, INDEX(emp_roman), PRIMARY KEY (emp_id) )' Skip_Counter: 0 Exec_Master_Log_Pos: 200 Relay_Log_Space: 924 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 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 1050 Last_SQL_Error: Error 'Table 'emp' already exists' on query. Default database: 'repl_test'. Query: 'CREATE TABLE emp ( emp_id INTEGER UNSIGNED AUTO_INCREMENT, emp_name VARCHAR(50) NOT NULL, emp_roman VARCHAR(50) NOT NULL, emp_email VARCHAR(100) NOT NULL, INDEX(emp_roman), PRIMARY KEY (emp_id) )' Replicate_Ignore_Server_Ids: Master_Server_Id: 1
この状態でMasterでデータを挿入するクエリを走らせるとMasterにはデータが追加される。
@ Master mysql> INSERT INTO emp (emp_name, emp_roman, emp_email) -> VALUES("Alice", "alice", "alice@example.com"), -> ("Bob", "bob", "bob@example.com"), -> ("Cris", "cris", "cris@example.com"); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0
しかし依然として、CREATE TABLEクエリでレプリケーションが詰まった状態でストップしている。
@ Slave mysql> show slave status \G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.33.10 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000003 Read_Master_Log_Pos: 856 Relay_Log_File: mysqld-relay-bin.000006 Relay_Log_Pos: 346 Relay_Master_Log_File: mysql-bin.000003 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: 1050 Last_Error: Error 'Table 'emp' already exists' on query. Default database: 'repl_test'. Query: 'CREATE TABLE emp ( emp_id INTEGER UNSIGNED AUTO_INCREMENT, emp_name VARCHAR(50) NOT NULL, emp_roman VARCHAR(50) NOT NULL, emp_email VARCHAR(100) NOT NULL, INDEX(emp_roman), PRIMARY KEY (emp_id) )' Skip_Counter: 0 Exec_Master_Log_Pos: 200 Relay_Log_Space: 1305 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 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 1050 Last_SQL_Error: Error 'Table 'emp' already exists' on query. Default database: 'repl_test'. Query: 'CREATE TABLE emp ( emp_id INTEGER UNSIGNED AUTO_INCREMENT, emp_name VARCHAR(50) NOT NULL, emp_roman VARCHAR(50) NOT NULL, emp_email VARCHAR(100) NOT NULL, INDEX(emp_roman), PRIMARY KEY (emp_id) )' Replicate_Ignore_Server_Ids: Master_Server_Id: 1
この状態から脱するために、SQL_SLAVE_SKIP_COUNTER でステートメントを1文スキップする。
@ Slave mysql> stop slave; Query OK, 0 rows affected (0.00 sec) mysql> set global SQL_SLAVE_SKIP_COUNTER = 1; Query OK, 0 rows affected (0.00 sec) mysql> start slave; Query OK, 0 rows affected (0.00 sec)
すると、レプリケーションが正常状態に戻る。
@ Slave mysql> show slave status \G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.33.10 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000003 Read_Master_Log_Pos: 856 Relay_Log_File: mysqld-relay-bin.000007 Relay_Log_Pos: 253 Relay_Master_Log_File: mysql-bin.000003 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 856 Relay_Log_Space: 1305 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: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1 1 row in set (0.00 sec)
うーん、このSKIP_COUNTERを増やしてステートメントを飛ばす運用はしたくないね。
Masterにmysqldumpで生成したクエリから流し込んでみる
今度も先にSlaveに新しいテーブルを作り、同様にMasterで同じテーブルを作ってみる。ただし、今回はSlaveにてmysqldumpを使ってテーブルを抽出して、Masterの方に流し込む。dumpしたクエリの先頭にはSlaveで生成したテーブルをDROPするクエリがはいるはずである。
@ Slave mysql> CREATE TABLE dept ( -> dept_id SMALLINT UNSIGNED, -> dept_name VARCHAR(50) NOT NULL, -> dep_tel VARCHAR(20) NOT NULL, -> PRIMARY KEY (dept_id) -> ); Query OK, 0 rows affected (0.01 sec)
@ Slave vagrant@mysql-slave1:~$ mysqldump -uroot repl_test emp > table_dump.sql vagrant@mysql-slave1:~$ ls table_dump.sql vagrant@mysql-slave1:~$ scp table_dump.sql vagrant@192.168.33.10:/tmp
@ Master vagrant@mysql-master:~$ cat /tmp/table_dump.sql -- -- Table structure for table `dept` -- DROP TABLE IF EXISTS `dept`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dept` ( `dept_id` smallint(5) unsigned NOT NULL DEFAULT '0', `dept_name` varchar(50) NOT NULL, `dep_tel` varchar(20) NOT NULL, PRIMARY KEY (`dept_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dept` -- LOCK TABLES `dept` WRITE;
@ Master vagrant@mysql-master:~$ mysql -uroot repl_test < /tmp/table_dump.sql vagrant@mysql-master:~$ mysql -uroot mysql> use repl_test; mysql> show tables; +---------------------+ | Tables_in_repl_test | +---------------------+ | dept | | emp | +---------------------+ 2 rows in set (0.00 sec) mysql> show master status \G *************************** 1. row *************************** File: mysql-bin.000003 Position: 2482 Binlog_Do_DB: Binlog_Ignore_DB: 1 row in set (0.00 sec)
Masterにdumpしたところで、Slaveの状態を確認するとレプリケーションが続いていることが確認できる。
@ Slave vagrant@mysql-slave1:~$ mysql -uroot mysql> show slave status \G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.33.10 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000003 Read_Master_Log_Pos: 2482 Relay_Log_File: mysqld-relay-bin.000007 Relay_Log_Pos: 1879 Relay_Master_Log_File: mysql-bin.000003 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 2482 Relay_Log_Space: 2931 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: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1
結論
SLAVEでMASTERに存在しないテーブルを作ることはレプリケーションの妨害にはならない。また、SLAVEで生成したテーブルをDROP IF EXISTS table_name
のようなクエリ、つまり、mysqldumpで生成したクエリをMasterで流せば、SLAVEでもMASTERに存在しないテーブルが消えるためレプリケーションは継続される。(SLAVEに渡したbinlog実行前はSLAVEのほうが進んでいる状態を取りうる)
参考
この2つの本はMySQLサーバの運用、管理についてかゆいところまで説明しているので良い。