MySQL Master-Master 复制和自动递增列问题

geo*_*kis 3 mysql replication auto-increment master-slave database-replication

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

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

-- Master1 -- 
auto_increment_increment = 2
auto_increment_offset = 1

-- Master2 -- 
auto_increment_increment = 2
auto_increment_offset = 2
Run Code Online (Sandbox Code Playgroud)

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

- Master1: 1,3,5,7,9,11,13  ...
- Master2: 2,4,6,8,10,12,14 ...
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

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

START SLAVE;
Run Code Online (Sandbox Code Playgroud)

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

  • 必须插入单个记录才能使进程起飞

    INSERT INTO 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:当然这种查询在生产应用中很少发生,但我相信它仍然证明了一点。

And*_*and 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(警告,不要在旧数据库上执行此操作,这将花费很长时间)。这就是我所看到的。

    查询 | BEGIN
    Intvar | INSERT_ID=3
    查询 | 使用test; INSERT INTO t1(c1,d1) SELECT ...
    查询| 提交
    查询 | BEGIN
    Intvar | INSERT_ID=5
    查询 | 使用test; INSERT INTO t1(c1,d1) SELECT ...
    查询| 提交
    查询 | BEGIN
    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. 复制的下一部分是这样的

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

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

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

也就是说,如果您需要做这样的事情,对于这种特殊情况有一个简单的解决方法。

  1. 在数据中添加一个 server_id 并创建一个这样的表

    创建表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 DEFAULT1CHARSET ;

  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 是下一个大于 3 且满足auto_increment_offset + N × auto_increment_increment)。

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

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

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

  1. 我用两台服务器和 master 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 行,并且二进制日志包含“服务器上使用的第一个自动增量”之类的内容,那么您将发生冲突。

我知道这是抽象和奇怪的,甚至比想象它更难写出来:)

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