Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

otter同步时,部分update的操作没有同步成功 #507

Closed
victorqian opened this issue Apr 10, 2018 · 28 comments
Closed

otter同步时,部分update的操作没有同步成功 #507

victorqian opened this issue Apr 10, 2018 · 28 comments
Assignees
Labels
Milestone

Comments

@victorqian
Copy link

有一种场景,业务上先insert一条数据,再update这条数据。otter进行同步时,update的这个操作丢失了,日志中也没有报错信息。是node在消费时因为多线程原因导致先操作了update失败,然后insert了数据吗?

@whb420502834
Copy link

看你的意思是说,insert和update是同一批数据,otter处理时,insert和update语句合并成insert语句,但结果应该是update的数据吧,新人如不对,请见谅,参考下吧。

@victorqian
Copy link
Author

你说的是otter的入库算法,但目前现实的情况是,update的binlog没有执行,最后只有insert的数据。

@daemonluo
Copy link

我也发现了这个问题。看了下源码,应该是数据入库算法有问题。同一个事务里,如果先插入一条数据,再更新这条数据,数据入库合并时,并不能合并成一条insert事件,这样事实际执行时,insert事件和update事件分别在不同的线程里执行,update事件就有可能不起作用。

@agapple
Copy link
Member

agapple commented Apr 17, 2018

insert和update,会合并为一条insert去执行

@victorqian
Copy link
Author

@agapple insert和update会合并的话,那能提供些思路帮助我们排查这种问题吗?从现象上看是update的操作丢了,并且没有任何错误日志,没法下手排查这种问题。

@victorqian
Copy link
Author

@daemonluo 那你们是如何避免这种问题的?业务层自己把insert和update合成一个insert操作吗?

@daemonluo
Copy link

@agapple 在同一批EventData中,insert和update的合并有个bug,RowKey这个对象在判断相等性和计算hashcode时,是直接调用EventColumn的相关方法,这样会把EventColumn的isUpdate属性计算进去,造成insert和update合并不成一个insert。

@LeoLiang666
Copy link

我这里也有这个问题,一段时间后,有一个表总条数是一样的,但某些数据有某些列的值不一样。估计是update没执行到。

@luyee
Copy link

luyee commented Apr 19, 2018

有这个问题的都用的哪个版本 ?我不知道是不是最近换了4.2.15的问题。。以前4.2.13好像没有~

@luyee
Copy link

luyee commented Apr 19, 2018

@daemonluo 看起来hashcode确实是

public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((columnName == null) ? 0 : columnName.hashCode());
    result = prime * result + columnType;
    result = prime * result + ((columnValue == null) ? 0 : columnValue.hashCode());
    result = prime * result + index;
    result = prime * result + (isKey ? 1231 : 1237);
    result = prime * result + (isNull ? 1231 : 1237);
    result = prime * result + (isUpdate ? 1231 : 1237);
    return result;
}

@daemonluo
Copy link

@luyee 我用的也是4.2.15

@paopao5541
Copy link

otter-4.2.14同样遇到这个状况@agapple ,这个有计划排查确认一下吗。

@cclouds
Copy link

cclouds commented May 21, 2018

otter-4.2.16也有这种情况 有没有哪个版本没有这个问题呢

@kitdine
Copy link

kitdine commented May 21, 2018

代码没有全部看完,发现一个问题,
MessageParser中,在解析update时,会在eventdata中放入keys以及oldkeys(oldkeys isupdate 为true,并且只在eventtype为update,!oldKeys.equals(keys)才会设置oldkeys):

} else if (eventType.isUpdate()) {
            // 获取变更前的主键.
            for (Column column : beforeColumns) {
                if (isKey(tableHolder, tableName, column)) {
                    oldKeyColumns.put(column.getName(), copyEventColumn(column, true, tableHolder));
                    // 同时记录一下new
                    // key,因为mysql5.6之后出现了minimal模式,after里会没有主键信息,需要在before记录中找
                    keyColumns.put(column.getName(), copyEventColumn(column, true, tableHolder));
                } else {
                    if (needAllColumns && entry.getHeader().getSourceType() == CanalEntry.Type.ORACLE) {
                        // 针对行记录同步时,针对oracle记录一下非主键的字段,因为update时针对未变更的字段在aftercolume里没有
                        notKeyColumns.put(column.getName(), copyEventColumn(column, isRowMode, tableHolder));
                    }
                }
            }
if (!keyColumns.isEmpty()) {
            eventData.setKeys(keys);
            if (eventData.getEventType().isUpdate() && !oldKeys.equals(keys)) { // update类型,如果存在主键不同,则记录下old
                                                                                // keys为变更前的主键
                eventData.setOldKeys(oldKeys);
            }

但是在DbLoadMerger中打印日志发现 keys和oldkeys是一样的(isupdate 为 false),这不和逻辑
transform过程中,·RowDataTransformer中,对于oldkeys的处理:

            List<EventColumn> tnewPks = new ArrayList<EventColumn>();
            List<EventColumn> toldPks = new ArrayList<EventColumn>();
            for (int i = 0; i < pks.size(); i++) {
                EventColumn newPk = pks.get(i);
                EventColumn oldPk = oldPks.get(i);
                // 转化new pk
                EventColumn tnewPk = translateColumn(data, newPk, tableHolder, dataMediaPair, translateColumnNames);
                if (tnewPk != null) {
                    tnewPks.add(tnewPk);
                    // 转化old pk,这里不能再用translateColumnNames了,因为转化new
                    // pk已经remove过一次view name了
                    toldPks.add(translateColumn(tnewPk, oldPk.getColumnValue(), dataMediaPair));
                }
            }

再次将oldkeys的isUpdate字段更新为keys的isUpdate的value:false,正确是应该仍然为oldkeys的值吧

@yuyid
Copy link

yuyid commented Jul 19, 2018

@kitdine isUpdate 正确的值应该 当前值isUpdate=false old值 isUpdate=true 所有才会导致EventColumn的hashcode 不一致
EventColumn
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((columnName == null) ? 0 : columnName.hashCode());
result = prime * result + columnType;
result = prime * result + ((columnValue == null) ? 0 : columnValue.hashCode());
result = prime * result + index;
result = prime * result + (isKey ? 1231 : 1237);
result = prime * result + (isNull ? 1231 : 1237);
result = prime * result + (isUpdate ? 1231 : 1237);
return result;
}
DbLoadMerger.RowKey
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((keys == null) ? 0 : keys.hashCode()); --直接调用EventColumn.hashcode
result = prime * result + ((schemaName == null) ? 0 : schemaName.hashCode());
result = prime * result + ((tableId == null) ? 0 : tableId.hashCode());
result = prime * result + ((tableName == null) ? 0 : tableName.hashCode());
return result;
}
最终的结果导致insert +update 没有合并 成一条insert 语句去执行
再多线程的情况下导致就有可能导致update语句不执行 特别是数据库insert 后马上做update
具体可以看DbLoadAction.doTwoPhase

@yuyid
Copy link

yuyid commented Jul 19, 2018

各位大佬最后需改RowKey 吗 update应该可以解决 别的会不会有影响 还没看完源码

@luyee
Copy link

luyee commented Jul 20, 2018

@yuyid 差不多就是RowKey跟EventColumn,可以参考下 记一次Otter中update bug

@yuyid
Copy link

yuyid commented Jul 20, 2018

@luyee 是的 这些都看过 我现在就是要评估这个改对 别的会不会有影响

@luyee
Copy link

luyee commented Jul 20, 2018

@yuyid 同担心 所以暂时单独给rowkey写了个相关的hashcode()~ EventColumn原来的的保留没动

@jeffreyji666
Copy link

@luyee 单独维护fix这个bug版的otter么?不知道官方什么时候会修复这个bug, 生产环境影响太大了。

@yuyid
Copy link

yuyid commented Jul 25, 2018

@agapple @daemonluo @luyee @victorqian
问题的根源在于RowDataTransformer
// 转化new pk
EventColumn tnewPk = translateColumn(data, newPk, tableHolder, dataMediaPair, translateColumnNames);
if (tnewPk != null) {
tnewPks.add(tnewPk);
// 转化old pk,这里不能再用translateColumnNames了,因为转化new
// pk已经remove过一次view name了
toldPks.add(translateColumn(tnewPk, oldPk.getColumnValue(), dataMediaPair));
}

修改后
//modify by yuyiding 20180725 主键的isupdate还是oldpk的isupdate
EventColumn transEventColumn = translateColumn(tnewPk, oldPk.getColumnValue(), dataMediaPair);
transEventColumn.setUpdate(oldPk.isUpdate());
toldPks.add(transEventColumn);

已经测试过insert +update merge成了一条insert (这个改动目前看不会影响别的)
请帮忙确认下这个改动的影响 🙏

@agapple
Copy link
Member

agapple commented Aug 1, 2018

我看一下这个问题

agapple added a commit that referenced this issue Aug 1, 2018
@agapple agapple closed this as completed Aug 1, 2018
@agapple
Copy link
Member

agapple commented Aug 1, 2018

修改的思路:将pk的isUpdate设置为true,因为pk的update有内置的逻辑来处理. 当时调整应该是为DRDS做的一个变更导致

@agapple agapple added the bug label Aug 1, 2018
@agapple agapple added this to the v4.2.16 milestone Aug 1, 2018
@agapple agapple self-assigned this Aug 1, 2018
@yuyid
Copy link

yuyid commented Aug 3, 2018 via email

@agapple
Copy link
Member

agapple commented Aug 13, 2018

8月份会发布一个修复版本

@agapple
Copy link
Member

agapple commented Sep 4, 2018

v4.2.16-alpha 2,打包了一个版本

@tangguol
Copy link

tangguol commented Oct 30, 2018

请问你们是怎么模拟出来的,我怎么模拟不出来insert+update不合并的情况。 otter版本4.2.13

@zzcclp
Copy link

zzcclp commented Mar 19, 2019

大神们,我使用4.2.16版本,发现在一个事务中,先insert后再update 100条记录,会有1-2十条数据update没成功,也没有错误,这个要怎么解决呢?

linqh1 pushed a commit to linqh1/otter that referenced this issue Dec 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests