3

我正在使用主-主复制进行一些测试,并且遇到了一些奇怪的问题,我将尝试描述我遵循的过程,以便有人可以重现该问题。

我在 2 个虚拟机上设置了复制,并在每个虚拟机的配置文件中设置了复制:

-- Master1 -- 
auto_increment_increment = 2
auto_increment_offset = 1

-- Master2 -- 
auto_increment_increment = 2
auto_increment_offset = 2

这些设置应导致自动增量列的算术级数:

- Master1: 1,3,5,7,9,11,13  ...
- Master2: 2,4,6,8,10,12,14 ...

Master1 得到奇数,Master2 得到偶数。然后我创建一个测试数据库并添加一个具有以下定义的表:

CREATE TABLE `t1` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `c1` varchar(50) DEFAULT NULL,
 `d1` date DEFAULT '1970-01-01',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM;

当然,数据库是在两台服务器上创建的。之后,我执行

START SLAVE;

在两台服务器上,以便复制开始。为了生成数据,我使用以下过程:

  • 必须插入一条记录才能使流程起飞

    插入 t1(c1,d1) SELECT LPAD('', 50, MD5( RAND() ) ), DATE_ADD( CURDATE(), INTERVAL FLOOR( RAND() * 365) DAY ) ;

  • 然后,您使用 INSERT - SELECT 从同一个表中以 2 n的速率开始插入,n是您执行查询的时间:

    INSERT INTO t1(c1,d1) SELECT LPAD('', 50, MD5( RAND() ) ), DATE_ADD( CURDATE(), INTERVAL FLOOR( RAND() * 365 ) DAY ) FROM t1;

提示:我发现这里描述的这种方法对于为您的表生成随机数据也非常方便。

因此,当我开始在两台服务器上同时执行这些查询时,这总是会导致自动增量列的复制重复键错误。如果有人有任何想法,我将不胜感激!

PS:当然这种查询在生产应用中很少发生,但我相信它仍然证明了一点。

4

1 回答 1

5

注意:我确实找到了答案,并将其放在首位。答案下方是其他一些咆哮(我最初的答案),它们仍然可以解释这一点。

由于您的查询将行数加倍,因此您的语句INSERT INTO t1(c1,d1) SELECT LPAD('', 50, MD5( RAND() ) ), DATE_ADD( CURDATE(), INTERVAL FLOOR( RAND() * 365 ) DAY ) FROM t1;可以在服务器 1 和服务器 2 上插入不同数量的行。所有使用自动增量列的语句都将其 INSERT_ID 与复制一起发送,并且该值在服务器 2 上不会为真如果 a 语句也已在那里运行。

让我们看一个例子。我会stop slave模拟一个长时间运行的查询或一个坏的网络。

  1. 创建两个数据库并设置主主复制
  2. 创建表并插入初始行
  3. 在服务器 2 上停止复制
  4. 在服务器 1 上运行使行数加倍的语句。2 次就足够了,但我做了 3 次。
  5. 检查show binlog events(警告,不要在旧数据库上执行此操作,这将需要很长时间)。这就是我所看到的。

    查询 | 开始Intvar
    | INSERT_ID=3
    查询 | 使用test;INSERT INTO t1(c1,d1) SELECT ...
    查询 | 提交
    查询 | 开始Intvar
    | INSERT_ID=5
    查询 | 使用test;INSERT INTO t1(c1,d1) SELECT ...
    查询 | 提交
    查询 | 开始Intvar
    | INSERT_ID=9
    查询 | 使用test;INSERT INTO t1(c1,d1) SELECT ... 查询 | 犯罪

  6. 请注意,每次我运行重复 INSERT_ID 都会相应地更改。在第二次插入时,它是 5,这意味着第一次插入插入了 1 行(请记住,增量为 2)。第三次插入 INSERT_ID 为 9 表示第二次插入插入了 2 行。这一切都说得通。让我们继续

  7. 在服务器 2 上进行一次复制,不要开始复制。正确执行 a select * from t1now 会显示两行,id 为 1 和 2。

  8. 现在再次启动从站并运行SHOW SLAVE STATUS \G. 它已停止,重复 id 为 5。再次从 t1 中选择所有值显示四行。第一个是最初的。第二个是我们在服务器 2 上所做的,最后一个 ID 为 3 和 5 的两个来自服务器 1 上的第一个语句,该语句仅添加了 1 行。

  9. 复制的下一部分是这个

    查询 | 开始Intvar
    | INSERT_ID=5
    查询 | 使用test;INSERT INTO t1(c1,d1) SELECT ...
    查询 | 犯罪

  10. 发生这种情况时,在服务器 1 上的 INSERT_ID 为 5,这就是复制将要使用的内容,但是,在服务器 2 上,我们已经有了 id 5,因为我们在得到它之前将行复制了一次。所以复制中断。

底线是这样的。在进行主-主复制时,每个语句都需要以相同的方式影响数据库。除其他外,添加或删除相同数量的行。

也就是说,如果您需要执行此类操作,则可以轻松解决此特殊情况。

  1. 将 server_id 添加到数据并创建一个像这样的表

    CREATE TABLE t1( idint(11) NOT NULL AUTO_INCREMENT, server_idint(1) DEFAULT NULL, c1varchar(50) DEFAULT NULL, d1date DEFAULT '1970-01-01', PRIMARY KEY ( id)) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1 ;

  2. 准备两行,每个服务器 id 一行

    INSERT INTO t1(server_id, c1,d1) SELECT 1, LPAD('', 50, MD5( RAND() ) ), DATE_ADD( CURDATE(), INTERVAL FLOOR( RAND() * 365 ) DAY ) ; INSERT INTO t1(server_id, c1,d1) SELECT 2, LPAD('', 50, MD5( RAND() ) ), DATE_ADD( CURDATE(), INTERVAL FLOOR( RAND() * 365 ) DAY ) ;

  3. 对于每个重复项,只需考虑在您的服务器上创建的行。

    INSERT INTO t1(server_id, c1,d1) SELECT server_id, LPAD('', 50, MD5( RAND() ) ), DATE_ADD( CURDATE(), INTERVAL FLOOR( RAND() * 365 ) DAY ) FROM t1 where server_id = 1个;

以下是原始答案

首先,当您假设您将有两组 id 范围为 1、3、5、.. 和 2、4、6 时,首先您的错误是,如果 Auto_increment 始终是,则无论该语句在哪个服务器上运行该值最大(id)+1。因此,如果您在服务器 1 上执行两次插入,它将获得奇数值 1 和 3。如果您随后在服务器 2 上执行一次插入,它将获得偶数值 4(4 是满足auto_increment_offset +的下一个大于 3 的数字N × auto_increment_increment)。

您可以通过运行查看 Auto_increment 值show table status;

其次,您在第一次插入之后的每个插入都会使表中的行数翻倍,这很快就会使其成为一个非常慢的操作,如果这与每个查询都非常慢有关,我不会感到惊讶。

也就是说,这就是我测试它的方式(并得到了同样令人惊讶的结果)。

  1. 我用两台服务器和 master 创建了一个新的空设置make_replication_sandbox --master_master mysql-5.5.17-osx10.6-x86_64.tar.gz。他们都开始了,还有奴隶。它们会在您进行设置时自动配置。
  2. 然后我根据您的问题创建了表格并插入了第一行。Auto_increment 现在在两台服务器上都是 2,并且表中有一行
  3. 然后我while (true) do ./n1 test -e "INSERT INTO t1(c1,d1) SELECT LPAD('', 50, MD5( RAND() ) ), DATE_ADD( CURDATE(), INTERVAL FLOOR( RAND() * 365 ) DAY ) FROM t1;"; done;同时在两台服务器上运行(另一台上是./n2)。

我有一个理论。

假设您在表中有 1000 行,并且您同时在两台服务器上启动了相同的复制。简而言之,您将在两台服务器上获得 4000 行,并且它们都是相同的。

但是发生的情况是,您复制每个数据库上的行,以便服务器 1 看到 2000 行和服务器 2000 行,但只有前 1000 行是相同的,另外 1000 行在两台服务器上生成不同。

然后复制开始。这是基于语句的复制,因此相同的语句运行意味着在两台服务器上,行再次复制到 4000,这是正确的计数,但仍然只有 1000 是相同的,其他 3000 会不同。

只要每台服务器运行相同数量的查询,这可能会起作用(没有重复,但数据不同)但是如果一台服务器设法在复制缓存之前运行两个查询,那么您会在复制中获得一条语句,在服务器 2 上添加了 1000 行(如果之前有 1000 行)但在服务器 1 上添加了 4000 行(因为服务器 1 已经将 1000 翻了两次)。如果下一条语句在服务器 2 上添加了另外 2000 行,并且二进制日志包含“服务器上使用的第一个自动增量”之类的内容,那么您将遇到冲突。

我知道这是抽象而奇怪的,而且比思考它更难写出来:)

我希望这有帮助,我希望这就是问题所在……师父很难,这绝对是我在师父中不会做的事情之一。

于 2011-11-19T23:46:49.283 回答