MySQL: mysqldbcompare – сравнение двух баз

Автор: | 09/09/2020
 

В качестве проложения поста AWS: Database Migration Service, часть 1 — обзор и пример миграции self-hosted MariaDB в AWS Aurora RDS – описание утилиты mysqldbcompare, которая предназначена для сравнения двух баз данных.

См. документацию тут>>>.

Установка

На Arch Linux можно установить из AUR:

[simterm]

$ yay -S mysql-utilities

[/simterm]

Либо скачать и установить вручную.

Загружаем архив:

[simterm]

$ wget http://cdn.mysql.com/Downloads/MySQLGUITools/mysql-utilities-1.6.5.tar.gz
$ tar xfp mysql-utilities-1.6.5.tar.gz
$ ll mysql-utilities-1.6.5
total 140
-rw-r--r-- 1 setevoy setevoy 37814 Jan 17  2017 CHANGES.txt
drwxr-xr-x 3 setevoy setevoy  4096 Jan 17  2017 docs
-rw-r--r-- 1 setevoy setevoy  6680 Jan 17  2017 info.py
-rw-r--r-- 1 setevoy setevoy 17987 Jan 17  2017 LICENSE.txt
drwxr-xr-x 4 setevoy setevoy  4096 Jan 17  2017 mysql
-rw-r--r-- 1 setevoy setevoy   928 Jan 17  2017 PKG-INFO
-rw-r--r-- 1 setevoy setevoy 34819 Jan 17  2017 README.txt
drwxr-xr-x 2 setevoy setevoy  4096 Jan 17  2017 scripts
-rw-r--r-- 1 setevoy setevoy 14232 Jan 17  2017 setup.py
drwxr-xr-x 2 setevoy setevoy  4096 Jan 17  2017 unit_tests

[/simterm]

Пробуем установить:

[simterm]

$ python mysql-utilities-1.6.5/setup.py 
Traceback (most recent call last):
  File "mysql-utilities-1.6.5/setup.py", line 21, in <module>
    import ConfigParser
ModuleNotFoundError: No module named 'ConfigParser'

[/simterm]

Добавляем Python-модуль configparser:

[simterm]

$ yay -S python-configparser

[/simterm]

И ещё раз установка:

[simterm]

$ python mysql-utilities-1.6.5/setup.py 
Traceback (most recent call last):
  File "mysql-utilities-1.6.5/setup.py", line 21, in <module>
    import ConfigParser
ModuleNotFoundError: No module named 'ConfigParser'

[/simterm]

Та что ж ты…

Но проблема старая, известная – открываем на редактирование файл setup.py, в импортах меняем:

...
import ConfigParser
...

На:

...
import configparser
...

И ещё раз запускаем сборку:

[simterm]

$ cd mysql-utilities-1.6.5
$ python2 setup.py build

[/simterm]

Проверяем каталог buid:

[simterm]

$ ll build/
total 8
drwxr-xr-x 3 setevoy setevoy 4096 Sep  2 10:04 lib
drwxr-xr-x 2 setevoy setevoy 4096 Sep  2 10:04 scripts-2.7

[/simterm]

Устанавливаем:

[simterm]

$ sudo python2 setup.py install
...
running install_scripts
...
copying build/scripts-2.7/mysqldiff -> /usr/bin
copying build/scripts-2.7/mysqldbcompare -> /usr/bin
...
copying build/scripts-2.7/mysqlrplshow -> /usr/bin
...

[/simterm]

Опции mysqldbcompare

mysqldbcompare может выполнить сравнение одной базы на разных серверах, разных баз на разных серверах, или одной базы на одном и том же сервере.

Общий синтаксис:

[simterm]

mysqldbcompare --server1=<DB_HOST_1> --server=<DB_HOST_2> <DB_NAME_1>:<DBNAME_2>

[/simterm]

Если имя баз данных на обоих хостах одинаковое – можно указать только <DBNAME_1>.

mysqldbcompare выполнит сравнение таблиц, views, триггеров, процедур, функций и events в каждой из заданных баз:

  1. проверяется наличие указанных баз на обоих хостах
  2. object-compare: проверка наличия всех объектов в обеих базах
  3. diff: сравнение CREATE объектов в обехи базах
  4. row-count: проверка количества записей в таблицах
  5. data-check: сравнение целостности записей в таблицах обеих баз

Любая из проверок может быть пропущенна используя --skip-<test-name>.

По-умолчанию mysqldbcompare остановит выполнение при первом найденном несоответствии. Что бы выполнить все тесты, игнорируя ошибки – используем --run-all-tests.

Опция --changes-for определяет направление проверок – для какой из баз будут выводить сообщения о необходимых трансформациях.

Если на сервере баз данных, учавствующем в проверке, включен binlog для репликации, и вы не хотите, что бы операции mysqldbcompare не попали в этот лог – используйте --disable-binary-logging.

Запуск и примеры

Итак, имеется два сервера баз данных, на каждом находится база, которая после миграции и репликации с помощью AWS Database Migration Service должна быть идентична базе на другом сервере.

При использовании баз с тире в имени – экранируйем из с помощью `, а для bash – символ ` экранируем с помощью \.

В примере ниже мы используем:

  • source DB – исходный сервер, с которого копировали базу – --server1=root:pass@104.***.***.2
  • target DB – целевой сервер, на который копировали базу – --server2=admin:pass@aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com

[simterm]

$ mysqldbcompare --server1=root:pass@104.***.***.2 --server2=admin:pass@aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com \`new-eat\`
# WARNING: Using a password on the command line interface can be insecure.
# server1 on 104.***.***.2: ... connected.
# server2 on aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com: ... connected.
# Checking databases new-eat on server1 and new-eat on server2
#
# Object definitions differ. (--changes-for=server1)
#

--- `new-eat`
+++ `new-eat`
@@ -1 +1 @@
-CREATE DATABASE `new-eat` /*!40100 DEFAULT CHARACTER SET utf8 */
+CREATE DATABASE `new-eat` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */

ERROR: The object definitions do not match.

[/simterm]

mysqldbcompare обнаружил первое несоответствие – разные кодировки для баз, и сразу остановил дальнейшую проверку.

Добавляем --run-all-tests или -a:

[simterm]

$ mysqldbcompare --run-all-tests --server1=root:password@104.***.***.2 --server2=dbadmin:password@aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.comm \`new-eat\`
# WARNING: Using a password on the command line interface can be insecure.
# server1 on 104.***.***.2: ... connected.
# server2 on aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com: ... connected.
# Checking databases new-eat on server1 and new-eat on server2
#
# Object definitions differ. (--changes-for=server1)
#

--- `new-eat`
+++ `new-eat`
@@ -1 +1 @@
-CREATE DATABASE `new-eat/*!40100 DEFAULT CHARACTER SET utf8 */
+CREATE DATABASE `new-eat` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */

#                                                   Defn    Row     Data   
# Type      Object Name                             Diff    Count   Check  
# ------------------------------------------------------------------------- 
# TABLE     acl_permissions                         pass    pass    -       
#           - Compare table checksum                                pass    
# TABLE     acl_permissions_acl_roles               pass    pass    -       
#           - Compare table checksum                                pass    
# TABLE     acl_roles                               pass    pass    -       
#           - Compare table checksum                                pass    
# TABLE     country_prices                          FAIL    pass    -       
#           - Compare table checksum                                pass    
#
# Object definitions differ. (--changes-for=server1)
#

--- `new-eat`.`country_prices`
+++ `new-eat`.`country_prices`
@@ -6,6 +6,6 @@
   `language_code` varchar(2) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
   `price` decimal(10,2) NOT NULL,
   `ccy` varchar(3) COLLATE utf8_unicode_ci NOT NULL,
-  `is_published` tinyint(1) NOT NULL DEFAULT 0,
+  `is_published` tinyint(1) NOT NULL DEFAULT '0',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
...

[/simterm]

Теперь мы дошли до проверки таблицы country_prices, у которой поле is_published в одном случае создано как “NOT NULL DEFAULT 0“, а во втором – “NOT NULL DEFAULT '0'“, при этом направление проверки – “--changes-for=server1“.

Изменим на --changes-for=server, что бы видеть что надо выполнить на таргет-сервере (AWS RDS Aurora) для того, что бы данные стали идентичны:

[simterm]

$ mysqldbcompare --run-all-tests --changes-for=server2 --server1=root:password@104.***.***.2 --server2=dbadmin:password@aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com \`new-eat\` --difftype=sql
# WARNING: Using a password on the command line interface can be insecure.
# server1 on 104.***.***.2: ... connected.
# server2 on aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com: ... connected.
# Checking databases new-eat on server1 and new-eat on server2
#
# Transformation for --changes-for=server2:
#

ALTER DATABASE new-eat CHARACTER SET utf8 COLLATE = utf8_general_ci;

#                                                   Defn    Row     Data
# Type      Object Name                             Diff    Count   Check
# -------------------------------------------------------------------------
# TABLE     acl_permissions                         pass    pass    -
#           - Compare table checksum                                pass
# TABLE     acl_permissions_acl_roles               pass    pass    -
#           - Compare table checksum                                pass
# TABLE     acl_roles                               pass    pass    -
#           - Compare table checksum                                pass
...
# TABLE     cron                                    FAIL    pass    -
#           - Compare table checksum                                pass
#
# Transformation for --changes-for=server2:
#

ALTER TABLE `new-eat`.`cron` 
  CHANGE COLUMN description description varchar(255) NOT NULL DEFAULT '\'\'', 
  CHANGE COLUMN created_at created_at timestamp NULL DEFAULT 'NULL', 
  CHANGE COLUMN updated_at updated_at timestamp NULL DEFAULT 'NULL', 
AUTO_INCREMENT=7;
...

[/simterm]

Теперь видим, что на server2 для таблицы cron не совпадает AUTO_INCREMENT.

Проверяем в исходной базе:

[simterm]

MariaDB [new-eat]> SELECT `AUTO_INCREMENT` FROM  INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'new-eat' AND   TABLE_NAME   = 'cron'\G
*************************** 1. row ***************************
AUTO_INCREMENT: 7

[/simterm]

В целевой:

[simterm]

MySQL [new-eat]> SELECT `AUTO_INCREMENT` FROM  INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'new-eat' AND   TABLE_NAME   = 'cron'\G
*************************** 1. row ***************************
AUTO_INCREMENT: 6

[/simterm]

Исходная база:

[simterm]

MariaDB [new-eat]> select id from cron;
+----+
| id |
+----+
|  1 |
|  3 |
|  5 |
+----+

[/simterm]

Целевая база:

[simterm]

MySQL [new-eat]> select id from cron;
+----+
| id |
+----+
|  1 |
|  3 |
|  5 |
+----+

[/simterm]

Данные одинаковые, но следующий инкремент различается.

Аналогично с другими таблицами, например в исходной таблице order_accessories:

[simterm]

MariaDB [new-eat]> SELECT `AUTO_INCREMENT` FROM  INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'new-eat' AND   TABLE_NAME   = 'order_accessories'\G
*************************** 1. row ***************************
AUTO_INCREMENT: 41273
1 row in set (0.107 sec)

[/simterm]

И целевой:

[simterm]

MySQL [new-eat]> SELECT `AUTO_INCREMENT` FROM  INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'new-eat' AND   TABLE_NAME   = 'order_accessories'\G
*************************** 1. row ***************************
AUTO_INCREMENT: 41272

[/simterm]

Снова в таргете на один меньше.

Меняем номер для таблицы cron на 7:

[simterm]

MySQL [new-eat]> ALTER TABLE cron AUTO_INCREMENT = 7;
Query OK, 0 rows affected (0.173 sec)
Records: 0  Duplicates: 0  Warnings: 0

[/simterm]

И заодно кодировку базы:

[simterm]

MySQL [new-eat]> ALTER DATABASE `new-eat` CHARACTER SET utf8 COLLATE = utf8_general_ci;
Query OK, 1 row affected (0.135 sec)

[/simterm]

Повторяем проверку:

[simterm]

$ mysqldbcompare --run-all-tests --changes-for=server2 --server1=root:password@104.***.***.2 --server2=dbadmin:password@aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com \`new-eat\` --difftype=sql
# WARNING: Using a password on the command line interface can be insecure.
# server1 on 104.***.***.2: ... connected.
# server2 on aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com: ... connected.
# Checking databases new-eat on server1 and new-eat on server2
#
#
# Type      Object Name                             Diff    Count   Check
# -------------------------------------------------------------------------
# TABLE     acl_permissions                         pass    pass    -
#           - Compare table checksum                                pass    
# TABLE     acl_permissions_acl_roles               pass    pass    -       
#           - Compare table checksum                                pass    
# TABLE     acl_roles                               pass    pass    -       
#           - Compare table checksum                                pass    
# TABLE     country_prices                          FAIL    pass    -       
#           - Compare table checksum                                pass    
#
# Transformation for --changes-for=server2:
...
# TABLE     cron                                    FAIL    pass    -       
#           - Compare table checksum                                pass    
#
# Transformation for --changes-for=server2:
#

ALTER TABLE `new-eat`.`cron` 
  CHANGE COLUMN description description varchar(255) NOT NULL DEFAULT '\'\'', 
  CHANGE COLUMN created_at created_at timestamp NULL DEFAULT 'NULL', 
  CHANGE COLUMN updated_at updated_at timestamp NULL DEFAULT 'NULL';
...

[/simterm]

Теперь вопросов к кодировке и AUTO_INCREMENT нет.

Но на всякий случай – пересоздал дамп с опциями --skip-lock-tables и --single-transaction, залил – теперь всё получилось ровно. Видимо в прошлый раз какая-то запись добавилась во время создания дампа.

Попробуем.

Дропаем базу в таргете:

[simterm]

MySQL [(none)]> drop database `new-eat`;

[/simterm]

Перездаём её с CHARACTER utf8:

[simterm]

MySQL [(none)]> create database `new-eat` CHARACTER SET utf8 COLLATE = utf8_general_ci;;

[/simterm]

Создаём новый дамп:

[simterm]

$ mysqldump -h 104.***.***.2 -u root -ppassword --no-data --skip-lock-tables --single-transaction new-eat > new-eat-prod-cloned.sql

[simterm]

У него по-прежнему 7:

...
-- Table structure for table `cron`
--

DROP TABLE IF EXISTS `cron`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `cron` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
...
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
...

Загружаем в новую базу:

[simterm]

$ mysql -h aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com -u dbadmin -ppassword new-eat < EAT-DBs/new-eat-prod-cloned.sql

[/simterm]

Проверяем авто-инкремент в новой базе:

[simterm]

MySQL [new-eat]> SELECT `AUTO_INCREMENT` FROM  INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'new-eat' AND   TABLE_NAME   = 'cron';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              7 |
+----------------+

[/simterm]

Теперь везде одинаково.

Повторяем, добавим --difftype=differ --format=vertical --width=150:

[simterm]

$ mysqldbcompare --run-all-tests --changes-for=server2 --server1=root:password@104.***.***.2 --server2=dbadmin:password@aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com \`new-eat\` --difftype=differ --format=vertical --width=150
# WARNING: Using a password on the command line interface can be insecure.
# server1 on 104.***.***.2: ... connected.
# server2 on aurora-web-dev-cluster.***.us-east-2.rds.amazonaws.com: ... connected.
# Checking databases new-eat on server1 and new-eat on server2
#
#                                                                                                                              Defn    Row     Data   
# Type      Object Name                                                                                                        Diff    Count   Check  
# ---------------------------------------------------------------------------------------------------------------------------------------------------- 
# TABLE     acl_permissions                                                                                                    pass    pass    -       
#           - Compare table checksum                                                                                                           pass    
# TABLE     acl_permissions_acl_roles                                                                                          pass    pass    -       
#           - Compare table checksum                                                                                                           pass    
# TABLE     acl_roles                                                                                                          pass    pass    -       
#           - Compare table checksum                                                                                                           pass    
# TABLE     country_prices                                                                                                     FAIL    pass    -       
#           - Compare table checksum                                                                                                           pass    
#
# Object definitions differ. (--changes-for=server2)
#

  CREATE TABLE `country_prices` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `created_at` timestamp NULL DEFAULT NULL,
    `updated_at` timestamp NULL DEFAULT NULL,
    `country_code` varchar(2) COLLATE utf8_unicode_ci NOT NULL,
    `language_code` varchar(2) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
    `price` decimal(10,2) NOT NULL,
    `ccy` varchar(3) COLLATE utf8_unicode_ci NOT NULL,
-   `is_published` tinyint(1) NOT NULL DEFAULT '0',
?                                              - -
+   `is_published` tinyint(1) NOT NULL DEFAULT 0,
    PRIMARY KEY (`id`)
  ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
...

[/simterm]

Так сообщение станет ещё более наглядным:

...
-   `is_published` tinyint(1) NOT NULL DEFAULT '0',
?                                              - -
+   `is_published` tinyint(1) NOT NULL DEFAULT 0,
...

В одном случае, в старой базе, у нас CREATE выполняется с DEFAULT '0', а в другом – без кавычек, DEFAULT 0.

Уже было, не критично, можно пропустить.

Аналогично – проверяем все остальные таблицы и данные в них.