前言:

今天遇到主从表不一致的情况,很奇怪为什么会出现不一致的情况,因为复制状态一直都是正常的。最后检查出现不一致的数据都是主键,原来是当时初始化数据的时候导致的。现在分析记录下这个问题,避免以后再遇到这个"坑"。

背景:

主从服务器,MIXED复制模式。

分析:

表:SPU

       Table: SPU
Create Table: CREATE TABLE `SPU` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`trademark` varchar(255) NOT NULL COMMENT '品牌',
`item_code` varchar(255) NOT NULL COMMENT '货号',
`product_id` int(10) DEFAULT '',
PRIMARY KEY (`id`),
KEY `trademark` (`trademark`),
KEY `item_code` (`item_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='SPU'

当时的初始化操作的SQL:

INSERT INTO SPU(trademark, item_code)
SELECT * FROM
(
SELECT distinct lower(TRIM(a.`value`)) col1, lower(TRIM(b.`value`)) col2
FROM
product_property a LEFT JOIN product_property b
ON
a.product_id = b.product_id
WHERE
a.property_id = 14 AND b.property_id = 9 AND a.value IS NOT NULL AND a.value <> ''
AND
b.value IS NOT NULL AND b.value <> ''
) as aa

上面的SQL执行完之后,主从表的COUNT数量一样,但SPU表有个自增主键,就在这里出现主从插入SPU的顺序不一样,即主从SELECT出来的结果顺序不一样。由于上面的结果集很大,所以就取前10条记录看看:

主:

现在表中数据的顺序:
zjy@192.168.10.23 : tt 04:38:48>select id,trademark col1,item_code col2 from SPU limit 10;
+----+-----------+-------------+
| id | col1 | col2 |
+----+-----------+-------------+
| 1 | 鼓浪屿 | gd rg |
| 2 | 山松 | 10020122 |
| 3 | coulter | 无 |
| 4 | oricell | mubmd-01101 |
| 5 | oricell | huxma-01101 |
| 6 | oricell | huxmf-01001 |
| 7 | oricell | rawmx-01001 |
| 8 | oricell | rasmx-01001 |
| 9 | oricell | rafmx-01101 |
| 10 | oricell | rbxmx-01001 |
+----+-----------+-------------+
10 rows in set (0.01 sec) 当时初始化的sql读出数据的顺序:
zjy@192.168.10.23 : tt 04:40:25>SELECT distinct lower(TRIM(a.`value`)) col1, lower(TRIM(b.`value`)) col2
-> FROM
-> product_property a LEFT JOIN product_property b
-> ON
-> a.product_id = b.product_id
-> WHERE
-> a.property_id = 14 AND b.property_id = 9 AND a.value IS NOT NULL AND a.value <> ''
-> AND
-> b.value IS NOT NULL AND b.value <> '' limit 10;
+-----------+-------------+
| col1 | col2 |
+-----------+-------------+
| 鼓浪屿 | gd rg |
| 山松 | 10020122 |
| coulter | 无 |
| oricell | mubmd-01101 |
| oricell | huxma-01101 |
| oricell | huxmf-01001 |
| oricell | rawmx-01001 |
| oricell | rasmx-01001 |
| oricell | rafmx-01101 |
| oricell | rbxmx-01001 |
+-----------+-------------+
10 rows in set (0.01 sec)

上面结果col1,col2的顺序一模一样。

从:

现在表中数据的顺序:
zjy@192.168.10.8 : tt 04:38:03>select id,trademark col1,item_code col2 from SPU limit 10;
+----+--------------------------------+-----------------+
| id | col1 | col2 |
+----+--------------------------------+-----------------+
| 1 | bio-rad | 125-0140 |
| 2 | oricell(tm) | huxma-90011 |
| 3 | oricell(tm) | huxmf-90011 |
| 4 | oricell(tm) | rawmx-90011 |
| 5 | oricell | rasmx-90011 |
| 6 | oricell | rafmx-90011 |
| 7 | oricell | rbxmx-90011 |
| 8 | 上海科技有限公司 | bsm03011 |
| 9 | oricell | caxmx-90011 |
| 10 | oricell(tm) | tedta-10001-100 |
+----+--------------------------------+-----------------+
10 rows in set (0.00 sec) 当时初始化的sql读出数据的顺序:
zjy@192.168.10.8 : tt 04:38:58>SELECT distinct lower(TRIM(a.`value`)) col1, lower(TRIM(b.`value`)) col2
-> FROM
-> product_property a LEFT JOIN product_property b
-> ON
-> a.product_id = b.product_id
-> WHERE
-> a.property_id = 14 AND b.property_id = 9 AND a.value IS NOT NULL AND a.value <> ''
-> AND
-> b.value IS NOT NULL AND b.value <> '' limit 10;
+--------------------------------+-----------------+
| col1 | col2 |
+--------------------------------+-----------------+
| bio-rad | 125-0140 |
| oricell(tm) | huxma-90011 |
| oricell(tm) | huxmf-90011 |
| oricell(tm) | rawmx-90011 |
| oricell | rasmx-90011 |
| oricell | rafmx-90011 |
| oricell | rbxmx-90011 |
| 上海科技有限公司 | bsm03011 |
| oricell | caxmx-90011 |
| oricell(tm) | tedta-10001-100 |
+--------------------------------+-----------------+
10 rows in set (0.01 sec)

上面结果col1,col2的顺序一模一样。

到此为止,大家就清楚为什么SPU表的数据不一致了,准确来说是主键对应的数据不一致。要是自增主键只是提升INNODB的性能,没有业务上的意义,那么对于产品来说是没有影响的,可以忽略这个问题。否则,就需要好好的处理这个问题了。从另一个方面来说,也是因为复制的模式是STATEMENT引发这个问题的,因为同一个QUERY在2个地方执行出的结果不一样;要是ROW的复制模式,主会把所有字段的记录全部传送给从,就不会出现这个问题。

进一步分析: 为什么一样的SQL在主从上跑出来的数据顺序不一样呢?

通过EXPLAIN 看到主上的QUERY 先读 b表,再读a表;而从上的则是先扫描a表,再读b表。出现这样的情况,就是数据在块里面分布不一致,导致索引利用的方式也不一样,最终影响优化器的选择。因为INNODB是索引组织表的,一旦走的索引不一样,就会导致数据以不同的顺序被扫描出来。上面的这些结果刚好被验证。SQL执行计划如下:

主:先b再a

zjy@192.168.10.23 : tt 09:18:04>explain SELECT distinct lower(TRIM(a.`value`)) col1, lower(TRIM(b.`value`)) col2 FROM      product_property a  LEFT JOIN product_property b  ON      a.product_id = b.product_id  WHERE      a.property_id = 14 AND b.property_id = 9 AND a.value IS NOT NULL AND a.value <> ''  AND      b.value IS NOT NULL AND b.value <> '';
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+---------+------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+---------+------------------------------+
| 1 | SIMPLE | b | ref | property_id,product_id | property_id | 4 | const | 4145932 | Using where; Using temporary |
| 1 | SIMPLE | a | ref | property_id,product_id | product_id | 4 | tt.b.product_id | 1 | Using where |
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+---------+------------------------------+
2 rows in set (0.01 sec)

从:先a再b

zjy@192.168.10.8 : tt 09:18:13>explain SELECT distinct lower(TRIM(a.`value`)) col1, lower(TRIM(b.`value`)) col2 FROM      product_property a  LEFT JOIN product_property b  ON      a.product_id = b.product_id  WHERE      a.property_id = 14 AND b.property_id = 9 AND a.value IS NOT NULL AND a.value <> ''  AND      b.value IS NOT NULL AND b.value <> '';
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+----------+------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+----------+------------------------------+
| 1 | SIMPLE | a | ref | property_id,product_id | property_id | 4 | const | 17079464 | Using where; Using temporary |
| 1 | SIMPLE | b | ref | property_id,product_id | product_id | 4 | tt.a.product_id | 5 | Using where |
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+----------+------------------------------+
2 rows in set (0.13 sec)

主从对比发现,他们的执行计划和各表走的索引都不一样,导致最后出来的顺序也不一样的(结果集是一样的),这就验证了分析说的情况。那要是执行计划和索引一致呢?接下来继续验证下:

进一步验证:

因为INNODB是索引组织表的,索引就是数据,要是主从的执行计划一样,则他们的结果会是?

主的执行计划:
zjy@192.168.10.23 : tt 05:42:15>explain SELECT distinct lower(TRIM(a.`value`)) col1, lower(TRIM(b.`value`)) col2
-> FROM
-> product_property a LEFT JOIN product_property b
-> ON
-> a.product_id = b.product_id
-> WHERE
-> a.property_id = 14 AND b.property_id = 9 AND a.value IS NOT NULL AND a.value <> ''
-> AND
-> b.value IS NOT NULL AND b.value <> '';
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+---------+------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+---------+------------------------------+
| 1 | SIMPLE | b | ref | property_id,product_id | property_id | 4 | const | 3771874 | Using where; Using temporary |
| 1 | SIMPLE | a | ref | property_id,product_id | product_id | 4 | tt.b.product_id | 1 | Using where |
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+---------+------------------------------+
2 rows in set (0.01 sec)

从的执行计划:

zjy@192.168.10.8 : tt 05:42:07>explain SELECT distinct lower(TRIM(a.`value`)) col1, lower(TRIM(b.`value`)) col2
-> FROM
-> product_property a LEFT JOIN product_property b
-> ON
-> a.product_id = b.product_id
-> WHERE
-> a.property_id = 14 AND b.property_id = 9 AND a.value IS NOT NULL AND a.value <> ''
-> AND
-> b.value IS NOT NULL AND b.value <> '';
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+----------+------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+----------+------------------------------+
| 1 | SIMPLE | b | ref | property_id,product_id | property_id | 4 | const | 17375012 | Using where; Using temporary |
| 1 | SIMPLE | a | ref | property_id,product_id | product_id | 4 | tt.b.product_id | 5 | Using where |
+----+-------------+-------+------+------------------------+-------------+---------+----------------------+----------+------------------------------+
2 rows in set (0.00 sec)

执行计划一样,都是先b表再a表,再重新执行初始化的SQL:

主:

zjy@192.168.10.23 : tt 05:42:26>SELECT distinct lower(TRIM(a.`value`)) col1, lower(TRIM(b.`value`)) col2
-> FROM
-> product_property a LEFT JOIN product_property b
-> ON
-> a.product_id = b.product_id
-> WHERE
-> a.property_id = 14 AND b.property_id = 9 AND a.value IS NOT NULL AND a.value <> ''
-> AND
-> b.value IS NOT NULL AND b.value <> '' limit 10
-> ;
+-----------+-------------+
| col1 | col2 |
+-----------+-------------+
| 鼓浪屿 | gd rg |
| 山松 | 10020122 |
| coulter | 无 |
| oricell | mubmd-01101 |
| oricell | huxma-01101 |
| oricell | huxmf-01001 |
| oricell | rawmx-01001 |
| oricell | rasmx-01001 |
| oricell | rafmx-01101 |
| oricell | rbxmx-01001 |
+-----------+-------------+
10 rows in set (0.00 sec)

从:

zjy@192.168.10.8 : tt 05:42:32>SELECT distinct lower(TRIM(a.`value`)) col1, lower(TRIM(b.`value`)) col2
-> FROM
-> product_property a LEFT JOIN product_property b
-> ON
-> a.product_id = b.product_id
-> WHERE
-> a.property_id = 14 AND b.property_id = 9 AND a.value IS NOT NULL AND a.value <> ''
-> AND
-> b.value IS NOT NULL AND b.value <> '' limit 10
-> ;
+-----------+-------------+
| col1 | col2 |
+-----------+-------------+
| 鼓浪屿 | gd rg |
| 山松 | 10020122 |
| coulter | 无 |
| oricell | mubmd-01101 |
| oricell | huxma-01101 |
| oricell | huxmf-01001 |
| oricell | rawmx-01001 |
| oricell | rasmx-01001 |
| oricell | rafmx-01101 |
| oricell | rbxmx-01001 |
+-----------+-------------+
10 rows in set (0.01 sec)

好了,要是执行计划一样,结果是:SQL在主从上跑出来的结果一致了。

PS:另一个方法就是用ROW模式,有兴趣的可以测试下。

总结:

主从复制在STATEMENT下面确实被忽略了一些问题,可以用ROW模式代替,但也要知道ROW模式有哪些问题,可以参考MySQL Binlog 【ROW】和【STATEMENT】选择。也要清楚数据在磁盘块里面分布不一致,影响优化器的选择而导致索引利用的方式也不一样,最终也影响到数据的顺序。

总之,在主从上执行一些比较大的数据量的操作(批量、初始化)的时候,尽可能的先去主从上查看他们的执行计划是否一样,走的索引是否一致,确保查询出来的结果一样。另:这篇文章:blog.xupeng.me/2013/10/11/mysql-replace-into-trap/(MySQL "replace into" 的坑) 也在一定程度上说明别用自增主键当成有意义的数据。

MySQL主从复制数据不一致问题【自增主键】的更多相关文章

  1. mysql 插入数据失败防止自增长主键增长的方法

    mysql设置了自增长主键ID,插入失败的那个自增长ID也加一的,比如失败5个,下一个成功的不是在原来最后成功数据加1,而是直接变成加6了,失败次数一次就自动增长1了,能不能让失败的不增长的? 或者说 ...

  2. mybatis框架(6)---mybatis插入数据后获取自增主键

    mybatis插入数据后获取自增主键 首先理解这就话的意思:就是在往数据库表中插入一条数据的同时,返回该条数据在数据库表中的自增主键值. 有什么用呢,举个例子: 你编辑一条新闻,同时需要给该新闻打上标 ...

  3. 【JAVA - SSM】之MyBatis插入数据后获取自增主键

    很多时候,我们都需要在插入一条数据后回过头来获取到这条数据在数据表中的自增主键,便于后续操作.针对这个问题,有两种解决方案: (1)先插入,后查询.我们可以先插入一条数据,然后根据插入的数据的各个字段 ...

  4. 【JavaEE】之MyBatis插入数据后获取自增主键

    很多时候,我们都需要在插入一条数据后回过头来获取到这条数据在数据表中的自增主键,便于后续操作.针对这个问题,有两种解决方案: 先插入,后查询.我们可以先插入一条数据,然后根据插入的数据的各个字段值,再 ...

  5. MySQL 8 新特性之自增主键的持久化

    自增主键没有持久化是个比较早的bug,这点从其在官方bug网站的id号也可看出(https://bugs.mysql.com/bug.php?id=199).由Peter Zaitsev(现Perco ...

  6. 关于mybatis用mysql时,插入返回自增主键的问题

    公司决定新项目用mybatis,虽然这个以前学过但是一直没用过都忘得差不多了,而且项目比较紧,也没时间去系统点的学一学,只好很粗略的百度达到能用的程度就行了. 其中涉及到插入实体要求返回主键id的问题 ...

  7. mybatis插入数据后返回自增主键ID详解

    1.场景介绍: ​ 开发过程中我们经常性的会用到许多的中间表,用于数据之间的对应和关联.这个时候我们关联最多的就是ID,我们在一张表中插入数据后级联增加到关联表中.我们熟知的mybatis在插入数据后 ...

  8. mybatis 设置新增数据后返回自增主键

    主要是注解@Options起作用,语句如下: @Insert({ "INSERT INTO application_open_up ( " + "app_open_hos ...

  9. 数据库插入数据返回当前自增主键ID值的方法

    当我们插入一条数据的时候,我们很多时候都想立刻获取当前插入的主键值返回以做它用.我们通常的做法有如下几种: 1. 先 select max(id) +1 ,然后将+1后的值作为主键插入数据库: 2. ...

随机推荐

  1. 【8-30】oracle数据库学习

    oracle安装:将两个文件合并 全局用户:achievec 口令:Admin123456 用户:scott 口令:tiger oracle开发工具: sqlplusw 和sqlplus和pl/sql ...

  2. NERD_commenter——VIM批量注释与反注释插件

    转自:http://www.xefan.com/archives/83568.html 这是对程序员非常实用的一款插件,支持多种语言的补全,还支持单行注释,批量注释,等各种命令映射. 使用方法,先下载 ...

  3. 利用CSS实现带相同间隔地无缝滚动动画

    说明:因为在移动上主要利用CSS来做动画,所以没有考虑其他浏览器的兼容性,只有-webkit这个前缀,如果需要其他浏览器,请自行补齐. 首先解释一下什么是无缝滚动动画, 例如下面的例子 See the ...

  4. List<List<double>> lsls = null; 根据double值来重新排序lsls...

    "确定:Node-data = (7,2).具体是:根据x维上的值将数据排序, 6个数据的中值(所谓中值,即中间大小的值)为7, 所以Node-data域位数据点(,).这样, 该节点的分割 ...

  5. 跨Controllers传数据

    今天遇到两个问题,第一个是跨controller传值,后一个是比较简单的linq数据库查询问题.先描述以下问题我有一个入库单和一个入库明细,入库的逻辑是先填写入库单在填入库明细.两者要么同时完成,要么 ...

  6. 【PHP面向对象(OOP)编程入门教程】18.__call()处理调用错误

    在程序开发中,如果在使用对象调用对象内部方法时候,调用的这个方法不存在那么程序就会出错,然后程序退出不能继续执行.那么可不可以在程序调用对象内部 不存在的方法时,提示我们调用的方法及使用的参数不存在, ...

  7. Mac Pro 软件收藏

    记录下Pro Mac中安装过的软件: 编号 软件名 功能说明 1 QQ   2 微信   3 搜狗输入法   4 Chrome 浏览器  Chrome 及其 插件“个性化设置”备份 5 360云盘   ...

  8. PHPCMS V9多站点[站群功能]动态设置与静态设置子站内容URL

    今天我们来讲解下 PHPCMS V9的站群功能的 动态站点与静态站点的配置 站群站点,分为动态站点,和静态站点两种设置方法: 静态的,就是将栏目和内容都了HTML 文件,我们先讲解下,站群的操作: 建 ...

  9. DataTable、List使用groupby进行分组和分组统计;List、DataTable查询筛选方法

    DataTable分组统计: .用两层循环计算,前提条件是数据已经按分组的列排好序的. DataTable dt = new DataTable(); dt.Columns.AddRange(new ...

  10. HDu1003(maxn sum)

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAABBcAAAMDCAYAAAD5XP0yAAAgAElEQVR4nOy97a8c133n2X+H3xjIC4