记录排查解决Hubble.Net链接Oracle数据库创建镜像库数据丢失的问题

原由

前几天在弄Hubble链接Oracle数据库,而后在mongodb中创建一个镜像数据库;mysql

发现一个问题,本来数据是11W,可是镜像库中只有6w多条;算法

刚开始觉得是没运行好,又rebuild了一下sql

结果变成了7w多,又rebuild,又变成了6w了............mongodb

rebuild了N次,基本上每次结果都是不同的数据库

准备调试

没办法只能下载源码调试一下;服务器

先把全部的dll都输出到同一个目录oracle

而后把Hubble的服务中止了,再把Hubble的全部文件拷贝到dll的输出目录ide

而后编译生成一下,将HubbleTask设为启动项目,直接启动sqlserver

这样就至关于启动服务器了性能

而后进入Hubble的安装目录,启动QueryAnalyzer.exe

 开始调试

由于是创建mongodb的镜像,因此找到Hubble.Code项目中的DBAdapter文件夹中的MongoAdapter.cs

找到方法public void MirrorInsert(IList<Document> docs)  插入镜像数据

设置断点,就能够了,而后找到QueryAnalyzer.exe中的rebuild,执行

其中的docs就是须要插入到monogodb的数据,每次5000个,运行几回后就发现了,每到5,6w左右数据的时候,就会传入一个1000多的数据,而后就没有下次了

就是后面的数据查询不到了,每次只能查到6w左右的数据,如今能够排除是插入数据失败的缘由

排除了monogodb插入错误的缘由后,开始检查Oracle查询数据是否准确

而后推测是在从Orcale获取数据的时候有问题?

打开并行任务瞄一下...(这里只是习惯的看一下,不必定每次都有效)

不过此次很巧的,发现了一个熟悉的方法(由于Orcale的驱动是从新实现的,因此这部分比较熟)

恰好和猜想也吻合了,这个是获取Oracle数据的地方

点进去看一下

查询语句彷佛有些问题看,这时还不肯定

好吧 我认可这段时间搞Oracle比较多,也吃过很多亏,因此看到这段语句的时候就感受不太对劲了

看看这个语句是何时,在什么状况下生成...

继续顺着代码的思路往下排查

仔细看这个代码就不难发现,他但愿的效果是,按照索引键排序,而后查找出最小的5000个记录

并保存最后一个(最大一个)索引键的值到from这个变量上

再看GetSelectSql(from)方法

        private string GetSelectSql(long from)
        {
            if (_DBProvider.Table.DBAdapterTypeName.IndexOf("sqlserver", StringComparison.CurrentCultureIgnoreCase) == 0)
            {
                return GetSqlServerSelectSql(from);
            }
            else if (_DBProvider.Table.DBAdapterTypeName.IndexOf("mongodb", StringComparison.CurrentCultureIgnoreCase) == 0)
            {
                return GetMongoDBSelectSql(from);
            }
            else if (_DBProvider.Table.DBAdapterTypeName.IndexOf("oracle", StringComparison.CurrentCultureIgnoreCase) == 0)
            {
                return GetOracleSelectSql(from);
            }
            else if (_DBProvider.Table.DBAdapterTypeName.IndexOf("mysql", StringComparison.CurrentCultureIgnoreCase) == 0)
            {
                return GetMySqlSelectSql(from);
            }
            else if (_DBProvider.Table.DBAdapterTypeName.IndexOf("sqlite", StringComparison.CurrentCultureIgnoreCase) == 0)
            {
                return GetSqliteSelectSql(from);
            }
            else
            {
                return GetSqlServerSelectSql(from);
            }

        }
GetSelectSql
private string GetOracleSelectSql(long from)
{
    string fields = GetOracleFieldNameList(from);

    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("select {0} from {1} where {4} > {2} and rownum <= {3} order by {4} ",
        fields, _DBProvider.Table.DBTableName, from, _Step, Oracle8iAdapter.GetFieldName(_DBProvider.Table.DocIdReplaceField));
    return sb.ToString();
}

猜测已经获得验证了, 下一次查询的时候 使用上次获得的from做为条件,继续搜索前5000条记录

如今若是接触过Oracle的应该知道了,Orcale中的rownum+orderby的效果和sqlserver中的top+orderby是彻底不同的

Oracle中orderby是等rownum执行完以后才执行的

产生bug的缘由

也就是说 orderby 搜索出前5000条记录,而后对这5000条记录排序!而不是对全表排序,取出前5000条记录

假设我有数据 5,8,4,3,10,2,9,1,6,7

每次取出5条 5,8,4,3,10 而后排序 3,4,5,8,10 ,获得最后一个记录的id=10

下次搜索的时候获取比10大的5条记录..结果就是0 因此2,9,1,6,7数据丢失了......

开始修改代码

第一次修改:

private string GetOracleSelectSql(long from)
{
    string fields = GetOracleFieldNameList(from);

    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("select * from (select {0} from {1} where {4} > {2} order by {4}) where rownum <= {3}",
        fields, _DBProvider.Table.DBTableName, from, _Step, Oracle8iAdapter.GetFieldName(_DBProvider.Table.DocIdReplaceField));
    return sb.ToString();
}

测试获得的结果就是 数据是正确的,可是效率很低

第二次修改

因此进过第二次修改:

private string GetOracleSelectSql(long from)
{
    string fields = GetOracleFieldNameList(from);
    string indexerField = Oracle8iAdapter.GetFieldName(_DBProvider.Table.DocIdReplaceField);
    string table = _DBProvider.Table.DBTableName;
    string where = indexerField + " > " + from;

    string sql = Oracle8iAdapter.GetOracleSelectSql(table, fields, indexerField, _Step, where);
    return sql;
}
/// <summary>
/// 组成Oracle可用的Sql语句
/// </summary>
/// <param name="table">操做表</param>
/// <param name="fields">返回列列名</param>
/// <param name="indexerField">索引列列名</param>
/// <param name="rowsCount">返回条数</param>
/// <param name="where">额外的where条件,不包含where关键字</param>
/// <returns></returns>
public static string GetOracleSelectSql(string table, string fields, string indexerField, System.Decimal rowsCount, string where)
{
    string sql = @"
SELECT {1}
FROM {0} A
WHERE EXISTS (SELECT 1 FROM (SELECT {2}
        FROM (SELECT B.{2}
               FROM {0} B
               {4}{5}
               ORDER BY B.{2})
               WHERE ROWNUM <= {3}) C
        WHERE C.{2} = A.{2})
ORDER BY {2}
";

    if (where == null)
    {
        return string.Format(sql, table, fields, indexerField, rowsCount, null, null, null);
    }
    else
    {
        return string.Format(sql, table, fields, indexerField, rowsCount, " WHERE ", where, " AND ");
    }
}

虽然仍是有一些不合理的地方,不过咱们讲究的是最小的改动

关联改动

一共有4个方法是须要改动的

Hubble.Core.Service.SynchronizeCanUpdate.GetOracleSelectSql

Hubble.Core.Service.SynchronizeCanUpdate.GetOracleTriggleSql

Hubble.Core.Service.SynchronizeAppendOnly.GetOracleSelectSql

QueryAnalyzer.FormRebuildTableOld.GetOracleSelectSql

另外以前的方法也有必定的修改

private List<Document> GetDocumentsForInsert(IDBAdapter dbAdapter, ref long from)
{
    List<Document> result = new List<Document>();

    dbAdapter.DBProvider = this._DBProvider;

    System.Data.DataSet ds = dbAdapter.QuerySql(GetSelectSql(from));

    StringBuilder sb = new StringBuilder();

    foreach (System.Data.DataRow row in ds.Tables[0].Rows)
    {
        result.Add(GetOneRowDocument(row));
        from = long.Parse(row[_DBProvider.Table.DocIdReplaceField].ToString());
    }

    return result;
}

改成

private List<Document> GetDocumentsForInsert(IDBAdapter dbAdapter, ref long from)
{
    List<Document> result = new List<Document>();

    dbAdapter.DBProvider = this._DBProvider;

    System.Data.DataSet ds = dbAdapter.QuerySql(GetSelectSql(from));

    if (ds.Tables[0].Rows.Count <= 0)
    {
        return result;
    }

    var table = ds.Tables[0];

    foreach (System.Data.DataRow row in table.Rows)
    {
        result.Add(GetOneRowDocument(row));
    }
    
    from = Convert.ToInt64(table.Rows[table.Rows.Count - 1][_DBProvider.Table.DocIdReplaceField]);

    return result;
}

结束了

至此就基本结束了,其实看了表层实现来讲,仍是有不少地方能够优化的,可是Hubble真正的性能优点在于他的高效的搜索算法,因此表层的这些都不是瓶颈点

就不作过多的改动了

相关文章
相关标签/搜索