記錄排查解決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真正的性能優點在於他的高效的搜索算法,因此表層的這些都不是瓶頸點

就不作過多的改動了

相關文章
相關標籤/搜索