Статьи

Как избежать хеш-коллизий при использовании функции MySQL CRC32

Первоначально Написал Арунджит Аравиндан

Pt-таблица-контрольная сумма Percona Toolkit выполняет онлайн-проверку целостности репликации, выполняя запросы контрольной суммы на главном сервере, что дает разные результаты для реплик, которые не соответствуют главному — и инструмент pt-table-sync эффективно синхронизирует данные между таблицами MySQL.

Инструменты по умолчанию используют CRC32. Другие хорошие варианты включают MD5 и SHA1. Если вы установили   пользовательскую функцию FNV_64 , pt-table-sync обнаружит ее и предпочтет использовать ее, потому что она намного быстрее встроенных. Вы также можете использовать MURMUR_HASH, если вы установили эту пользовательскую функцию. Оба они распространяются вместе с Maatkit. Для получения дополнительной информации см. Документацию инструмента.

Ниже приведены контрольные примеры, аналогичные тем, с которыми вы могли столкнуться. Используя контрольную сумму таблицы, мы можем подтвердить, что две таблицы идентичны и полезны для проверки того, что подчиненный сервер синхронизирован с его ведущим. Следующие тесты с pt-table-checkum и pt-table-sync помогут вам более точно использовать инструменты.

Например, в настройке ведущий-ведомый у нас есть таблица с первичным ключом в столбце «a» и уникальным ключом в столбце «b». Здесь главная и подчиненная таблицы не синхронизированы, и таблицы имеют два одинаковых значения и два разных значения. Средство проверки контрольной суммы pt-таблицы должно быть в состоянии идентифицировать разницу между ведущим и ведомым устройствами, а синхронизация pt-table-sync в этом случае должна синхронизировать таблицы с двумя запросами REPLACE.

+-----+-----+    +-----+-----+
|  a  |  b  |    |  a  |  b  |
+-----+-----+    +-----+-----+
|  2  |  1  |    |  2  |  1  |
|  1  |  2  |    |  1  |  2  |
|  4  |  3  |    |  3  |  3  |
|  3  |  4  |    |  4  |  4  |
+-----+-----+    +-----+-----+

Случай 1: Некриптографическая хеш-функция (CRC32) и коллизия хеша.

Таблицы в исходном и целевом элементах имеют два разных столбца, и в общем случае инструменты должны определять разницу. Но нижеприведенные сценарии объясняют, как инструменты могут быть использованы неправильно и как их избежать, и делают вещи более последовательными и надежными при использовании инструментов в вашем производстве.

Инструменты по умолчанию используют контрольные суммы CRC32, и это склонно к хэш-конфликтам. В приведенном ниже случае некриптографическая функция (CRC32) не может идентифицировать два разных значения, так как функция генерирует одно и то же значение, даже если у нас есть разные значения в таблицах.

CREATE TABLE `t1` (
  `a` int(11) NOT NULL,
  `b` int(11) NOT NULL,
  PRIMARY KEY (`a`),
  UNIQUE KEY `b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Master           Slave
+-----+-----+    +-----+-----+
|  a  |  b  |    |  a  |  b  |
+-----+-----+    +-----+-----+
|  2  |  1  |    |  2  |  1  |
|  1  |  2  |    |  1  |  2  |
|  4  |  3  |    |  3  |  3  |
|  3  |  4  |    |  4  |  4  |
+-----+-----+    +-----+-----+
Master:
[root@localhost mysql]#  pt-table-checksum --replicate=percona.checksum --create-replicate-table --databases=db1 --tables=t1
localhost --user=root --password=*** --no-check-binlog-format
            TS ERRORS  DIFFS     ROWS  CHUNKS SKIPPED    TIME TABLE
09-17T00:59:45      0      0        4       1       0   1.081 db1.t1
Slave:
[root@localhost bin]# ./pt-table-sync --print --execute --replicate=percona.checksum --tables db1.t1 --user=root --password=***
 --verbose  --sync-to-master  192.**.**.**
# Syncing via replication h=192.**.**.**,p=...,u=root
# DELETE REPLACE INSERT UPDATE ALGORITHM START    END      EXIT DATABASE.TABLE

Сужено до BIT_XOR:

Master:
mysql> SELECT BIT_XOR(CAST(CRC32(CONCAT_WS('#', `a`, `b`)) AS UNSIGNED)) FROM `db1`.`t1`;
+------------------------------------------------------------+
| BIT_XOR(CAST(CRC32(CONCAT_WS('#', `a`, `b`)) AS UNSIGNED)) |
+------------------------------------------------------------+
|                                                    6581445 |
+------------------------------------------------------------+
1 row in set (0.00 sec)
Slave:
mysql> SELECT BIT_XOR(CAST(CRC32(CONCAT_WS('#', `a`, `b`)) AS UNSIGNED)) FROM `db1`.`t1`;
+------------------------------------------------------------+
| BIT_XOR(CAST(CRC32(CONCAT_WS('#', `a`, `b`)) AS UNSIGNED)) |
+------------------------------------------------------------+
|                                                    6581445 |
+------------------------------------------------------------+
1 row in set (0.16 sec)

Случай 2. Поскольку инструменты не могут определить разницу, давайте добавим новую строку в ведомое устройство и проверим, способны ли инструменты идентифицировать различные значения. Поэтому я добавляю новую строку (5,5) к ведомому.

mysql> insert into db1.t1 values(5,5);
Query OK, 1 row affected (0.05 sec)
Master           Slave
+-----+-----+    +-----+-----+
|  a  |  b  |    |  a  |  b  |
+-----+-----+    +-----+-----+
|  2  |  1  |    |  2  |  1  |
|  1  |  2  |    |  1  |  2  |
|  4  |  3  |    |  3  |  3  |
|  3  |  4  |    |  4  |  4  |
+-----+-----+    |  5  |  5  |
                 +-----+-----+
 [root@localhost mysql]#  pt-table-checksum --replicate=percona.checksum --create-replicate-table --databases=db1 --tables=t1
 localhost --user=root --password=*** --no-check-binlog-format
            TS ERRORS  DIFFS     ROWS  CHUNKS SKIPPED    TIME TABLE
09-17T01:01:13      0      1        4       1       0   1.054 db1.t1
[root@localhost bin]# ./pt-table-sync --print --execute --replicate=percona.checksum --tables db1.t1 --user=root --password=***
 --verbose  --sync-to-master  192.**.**.**
# Syncing via replication h=192.**.**.**,p=...,u=root
# DELETE REPLACE INSERT UPDATE ALGORITHM START    END      EXIT DATABASE.TABLE
DELETE FROM `db1`.`t1` WHERE `a`='5' LIMIT 1 /*percona-toolkit src_db:db1 src_tbl:t1 src_dsn:P=3306,h=192.**.**.**.
10,p=...,u=root dst_db:db1 dst_tbl:t1 dst_dsn:h=192.**.**.**,p=...,u=root lock:1 transaction:1 changing_src:percona.checksum
replicate:percona.checksum bidirectional:0 pid:5205 user:root host:localhost.localdomain*/;
REPLACE INTO `db1`.`t1`(`a`, `b`) VALUES ('3', '4') /*percona-toolkit src_db:db1 src_tbl:t1 src_dsn:P=3306,h=192.**.**.**,
p=...,u=root dst_db:db1 dst_tbl:t1 dst_dsn:h=192.**.**.**,p=...,u=root lock:1 transaction:1 changing_src:percona.checksum
 replicate:percona.checksum bidirectional:0 pid:5205 user:root host:localhost.localdomain*/;
REPLACE INTO `db1`.`t1`(`a`, `b`) VALUES ('4', '3') /*percona-toolkit src_db:db1 src_tbl:t1 src_dsn:P=3306,h=192.**.**.**,
p=...,u=root dst_db:db1 dst_tbl:t1 dst_dsn:h=192.**.**.**,p=...,u=root lock:1 transaction:1 changing_src:percona.checksum
replicate:percona.checksum bidirectional:0 pid:5205 user:root host:localhost.localdomain*/;
#      1       2      0      0 Chunk     01:01:43 01:01:43 2    db1.t1

Что ж, очевидно, инструменты теперь могут идентифицировать вновь добавленную строку в ведомом устройстве, а две другие строки имеют разницу.

Случай 3: Преимущество криптографических хеш-функций (например, Secure MD5)

Поэтому давайте составим таблицы, как в случае case1, и попросим инструменты использовать криптографические (безопасные MD5) хеш-функции вместо обычной некриптографической функции. Функция CRC32 по умолчанию не обеспечивает безопасности из-за их простой математической структуры и слишком склонна к хэш-конфликтам, но MD5 обеспечивает лучший уровень целостности. Итак, давайте попробуем с –function = md5 и посмотрим результат.

Master           Slave
+-----+-----+    +-----+-----+
|  a  |  b  |    |  a  |  b  |
+-----+-----+    +-----+-----+
|  2  |  1  |    |  2  |  1  |
|  1  |  2  |    |  1  |  2  |
|  4  |  3  |    |  3  |  3  |
|  3  |  4  |    |  4  |  4  |
+-----+-----+    +-----+-----+

Сужено до BIT_XOR:

Master:
mysql> SELECT 'test', 't2', '1', NULL, NULL, NULL, COUNT(*) AS cnt, COALESCE(LOWER(CONCAT(LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING
(@crc, 1, 16), 16, 10) AS UNSIGNED)), 10, 16), 16, '0'), LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc := md5(CONCAT_WS('#', `a`, `b`))
, 17, 16), 16, 10) AS UNSIGNED)), 10, 16), 16, '0'))), 0) AS crc FROM `db1`.`t1`;
+------+----+---+------+------+------+-----+----------------------------------+
| test | t2 | 1 | NULL | NULL | NULL | cnt | crc                              |
+------+----+---+------+------+------+-----+----------------------------------+
| test | t2 | 1 | NULL | NULL | NULL |   4 | 000000000000000063f65b71e539df48 |
+------+----+---+------+------+------+-----+----------------------------------+
1 row in set (0.00 sec)
Slave:
mysql> SELECT 'test', 't2', '1', NULL, NULL, NULL, COUNT(*) AS cnt, COALESCE(LOWER(CONCAT(LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING
(@crc, 1, 16), 16, 10) AS UNSIGNED)), 10, 16), 16, '0'), LPAD(CONV(BIT_XOR(CAST(CONV(SUBSTRING(@crc := md5(CONCAT_WS('#', `a`, `b`))
, 17, 16), 16, 10) AS UNSIGNED)), 10, 16), 16, '0'))), 0) AS crc FROM `db1`.`t1`;
+------+----+---+------+------+------+-----+----------------------------------+
| test | t2 | 1 | NULL | NULL | NULL | cnt | crc                              |
+------+----+---+------+------+------+-----+----------------------------------+
| test | t2 | 1 | NULL | NULL | NULL |   4 | 0000000000000000df024e1a4a32c31f |
+------+----+---+------+------+------+-----+----------------------------------+
1 row in set (0.00 sec)
[root@localhost mysql]# pt-table-checksum --replicate=percona.checksum --create-replicate-table --function=md5 --databases=db1
 --tables=t1 localhost --user=root --password=*** --no-check-binlog-format
            TS ERRORS  DIFFS     ROWS  CHUNKS SKIPPED    TIME TABLE
09-23T23:57:52      0      1       12       1       0   0.292 db1.t1
[root@localhost bin]# ./pt-table-sync --print --execute --replicate=percona.checksum --tables db1.t1 --user=root --password=amma
 --verbose --function=md5 --sync-to-master  192.***.***.***
# Syncing via replication h=192.168.56.102,p=...,u=root
# DELETE REPLACE INSERT UPDATE ALGORITHM START    END      EXIT DATABASE.TABLE
REPLACE INTO `db1`.`t1`(`a`, `b`) VALUES ('3', '4') /*percona-toolkit src_db:db1 src_tbl:t1 src_dsn:P=3306,h=192.168.56.101,p=...,
u=root dst_db:db1 dst_tbl:t1 dst_dsn:h=192.***.***.***,p=...,u=root lock:1 transaction:1 changing_src:percona.checksum
replicate:percona.checksum bidirectional:0 pid:5608 user:root host:localhost.localdomain*/;
REPLACE INTO `db1`.`t1`(`a`, `b`) VALUES ('4', '3') /*percona-toolkit src_db:db1 src_tbl:t1 src_dsn:P=3306,h=192.168.56.101,p=...,
u=root dst_db:db1 dst_tbl:t1 dst_dsn:h=192.***.**.***,p=...,u=root lock:1 transaction:1 changing_src:percona.checksum
replicate:percona.checksum bidirectional:0 pid:5608 user:root host:localhost.localdomain*/;
#      0       2      0      0 Chunk     04:46:04 04:46:04 2    db1.t1
Master           Slave
+-----+-----+    +-----+-----+
|  a  |  b  |    |  a  |  b  |
+-----+-----+    +-----+-----+
|  2  |  1  |    |  2  |  1  |
|  1  |  2  |    |  1  |  2  |
|  4  |  3  |    |  4  |  3  |
|  3  |  4  |    |  3  |  4  |
+-----+-----+    +-----+-----+

MD5 добился цели и решил проблему. См. Результат BIT_XOR для MD5, приведенный выше, и функция способна идентифицировать различные значения в таблицах и приводит к различным значениям crc. MD5 (Message-Digest алгоритм 5) — это хорошо известная криптографическая хеш-функция с результирующим 128-битным хеш-значением. MD5 широко используется в приложениях, связанных с безопасностью, а также часто используется для проверки целостности, но MD5 () и SHA1 () сильно нагружают ЦП с более медленным контрольным суммированием, если включено время фрагмента.