数据同步方法总结

软件开发过程中经常需要同步数据,如 C/S 程序为了支持离线应用需要定时把服务端的数据同步到客户端、向第三方提供提供数据等。

下面列举了常见的同步数据的方法:

(不推荐通过 SSIS 包进行数据同步,因为配置太多且出错后无法调试,推荐通过程序进行同步)

a)当同步的数据比较少时

可以在每次同步时先清空客户端(目标)数据库表(truncate/delete)的数据,然后直接从服务器写入最新的所有数据到客户端客户端数据库表。

优点:程序简单,不需要区分哪些是新增的数据哪些是修改的数据。

缺点:每次清空客户端数据库可能会产生大量的日志记录,这时需要定时清理日志;当数据量很大时,全表扫描会产生性能问题。

b)当同步的数据比较多时(如:照片表可能比较大)

1.可以在每次同步时先清空客户端(目标)数据库表(truncate/delete)的数据,然后把服务器端大量的数据按分页的方式写到客户端数据库表。 

    public class SynchronizeSqlStr
    {
        public static readonly string TruncateSql = null;
        public static readonly string[] GetRecordCountStr = null;
        public static readonly string[] AllSynchronizeSqlStr = null;        

        static SynchronizeSqlStr()
        {
            TruncateSql = @"TRUNCATE TABLE T_Target;";

            GetRecordCountStr = new string[] {
                "SELECT COUNT(1) AS Co FROM T_Source;",
            };

            AllSynchronizeSqlStr = new string[]
            {
                @"INSERT INTO dbo.T_Target(TId, TName, DeptId)
                    SELECT S.TId, S.TName, S.DeptId FROM (SELECT ROW_NUMBER() OVER (ORDER BY TId) AS ROWNUM, TId, TName, DeptId FROM dbo.T_Source) AS S
                    WHERE S.ROWNUM >= {0} AND S.ROWNUM < {1};",
            };            
        }
    }
    public class HelloJob : IJob
    {
        private static readonly NLog.Logger log = NLog.LogManager.GetCurrentClassLogger();
        private static readonly string connStr = ConfigurationManager.AppSettings["ConnectionString"].ToString();
        int pageSize = 1000; // 默认每次同步1000条记录                

        public virtual Task Execute(IJobExecutionContext context)
        {
            try
            {
                DbHelperSQL.connectionString = DESEncrypt.Decrypt(connStr);

                // 步骤一:清理客户端的历史数据(注意:照片表采取增量同步方式,且不删除历史记录)
                DbHelperSQL.ExecuteSqlTran(SynchronizeSqlStr.TruncateSql);

                // 步骤二:依次同步服务器的数据到客户端
                for (int i = 0; i < SynchronizeSqlStr.AllSynchronizeSqlStr.Length; i++)
                {
                    try
                    {
                        log.Info(string.Format("准备异步执行脚本:" + i.ToString() + "  - {0}", DateTime.Now));

                        Thread thread = new Thread(SynchronizeDataByPager);
                        thread.IsBackground = true;
                        thread.Start(SynchronizeSqlStr.GetRecordCountStr[i] + "~" + SynchronizeSqlStr.AllSynchronizeSqlStr[i]);
                    }
                    catch (Exception e)
                    {
                        log.Error("异步执行脚本:" + i.ToString() + " 时出错:\r\n" + e.ToString() + " " + DateTime.Now + "\r\n");
                        break;
                    }

                    Thread.Sleep(200);
                }

                log.Info(string.Format("Synchronize 完成! - {0}", DateTime.Now));

                return Task.FromResult(true);
            }
            catch (Exception ex)
            {
                log.Error("执行Execute方法时出错:" + ex.ToString() + " " + DateTime.Now + "\r\n");
                return Task.FromResult(false);
            }
        }
        
        /// <summary>
        /// 按记录总条数进行分页,然后每次同步一页的数据,防止数据库操作超时
        /// </summary>
        private void SynchronizeDataByPager(object querySqlAndUpdateSql)
        {
            try
            {
                string sql = Convert.ToString(querySqlAndUpdateSql);
                if (string.IsNullOrEmpty(sql))
                    return;

                string querySql = sql.Split(new char[] { '~' })[0];
                string updateSql = sql.Split(new char[] { '~' })[1];

                DataSet ds = DbHelperSQL.Query(querySql);
                if (ds == null || ds.Tables == null || ds.Tables.Count < 1 || ds.Tables[0].Rows == null || ds.Tables[0].Rows.Count < 1)
                    return;

                int recordCount = 0;
                int.TryParse(ds.Tables[0].Rows[0]["Co"].ToString(), out recordCount);
                if (recordCount < 1)
                    return;

                int pageCount = recordCount / pageSize + 1;

                for (int i = 0; i < pageCount; i++)
                {
                    string eachPageSql = string.Format(updateSql, i * pageSize, i * pageSize + pageSize);
                    DbHelperSQL.ExecuteSqlTran(eachPageSql);

                    Thread.Sleep(TimeSpan.FromMilliseconds(100));
                }

            }
            catch (Exception ex)
            {
                log.Error("执行分页同步操作时出错:" + ex.ToString() + " " + DateTime.Now + "\r\n");
            }
        }
    }

2.如果源数据库中的表设计规范,即表中包含 Id、CreateTime、UpdateTime、IsDelete 字段,而且没有物理删除,这时在每次同步时可以先取目的表中最新的 UpdateTime 值,然后从源表中筛选大于或等于 UpdateTime 的记录,然后通过比对 Id 来区分哪些是新增记录,哪些是修改记录(从源表中筛选大于目标表最大 Id 的记录,即为新增的记录,其他的则是修改的记录),然后分别新增或修改到目的表中。

CREATE PROCEDURE [DBO].[P_SynchronizeData]
AS
BEGIN    
    SET NOCOUNT ON;
    DECLARE @Max_UpdateTime NVARCHAR(100);
    DECLARE @SQL1 NVARCHAR(1000);
    DECLARE @SQL2 NVARCHAR(1000);
    
    SELECT @Max_UpdateTime = MAX(UpdateTime) FROM T_Target;
    IF (@Max_UpdateTime IS NULL) 
    BEGIN
        INSERT INTO T_Target(Id,Name)
        SELECT * FROM OPENQUERY(HTIMSDB,'SELECT Id,Name FROM T_Source ');
    END
    ELSE
    BEGIN
        --先筛选出新增的记录,然后新增
        SET @SQL1 = 'SELECT Id,Name FROM T_Source WHERE UpdateTime >'''+ @Max_UpdateTime + ''''; 
        SET @SQL2 = 'INSERT INTO T_Target(Id,Name) VALUES(@SQL1)'            
        EXEC(@SQL2)

        --先筛选出修改的记录,然后修改,修改时可以先进行物理删除然后进行新增
    END
END

3.如果源数据库表设计粗糙,没有 UpdateTime 字段,而且还可能存在物理删除,这时需分两种情况:

3.1如果不允许在源数据库中创建触发器,由于无法区分新增还是修改而且无法直接找到被删除的记录,则只能进行全表比对或者按 1. 中的方法按分页的方式进行同步了。

3.2 如果允许在源数据库中创建触发器和表,则可以在源表中创建相应的触发器来监控增删改操作,然后把操作的表名、类型、主键等关键字段值保存到新建的中间表中,最后创建对应的目的表(包含 Id、CreateTime、UpdateTime、IsDelete、RecordCount 字段,这样如果需要同步这里的数据会简单很多),通过后台程序定时同步数据到目的表中。

(问题:当执行的语句中包含了增、删、改,即不是一种操作时,这时下面的触发器都无法区分操作类型,此方法不适应这种情况)

如下所示:

--中间表
CREATE TABLE [dbo].[SKEP_SYNC](
    [TableName] [nvarchar](200) NOT NULL,
    [Type] [smallint] NOT NULL,
    [PrimaryFeild] [nvarchar](200) NOT NULL,
    [PrimaryKey] [nvarchar](200) NOT NULL,
    [CreateTime] [datetime] NULL
) ON [PRIMARY]
--把触发器中的公共操作部分提取出来
CREATE PROCEDURE [dbo].[P_TRIGGER_COMMON]
    @SOURCE_TABLE_NAME nvarchar(200),
    @SOURCE_PRIMARYKEY_NAME nvarchar(200),
    @SOURCE_PRIMARYKEY_VALUE nvarchar(200),
    @TYPE SMALLINT
AS
BEGIN
    SET NOCOUNT ON;

    if(not exists(select 1 from [SKEP_DAS].[DBO].[SKEP_SYNC] where [TABLENAME] = @SOURCE_TABLE_NAME  and [TYPE] = @TYPE and [PRIMARYKEY] = @SOURCE_PRIMARYKEY_VALUE))
    begin
        INSERT INTO [SKEP_DAS].[DBO].[SKEP_SYNC]([TABLENAME],[TYPE],[PrimaryFeild],[PrimaryKey],[CreateTime]) 
        VALUES(@SOURCE_TABLE_NAME, @TYPE, @SOURCE_PRIMARYKEY_NAME, @SOURCE_PRIMARYKEY_VALUE, GETDATE());
    end
    else
    begin
        UPDATE [SKEP_DAS].[DBO].[SKEP_SYNC] 
        SET [CreateTime] = GETDATE() 
        where [TABLENAME] = @SOURCE_TABLE_NAME 
                and [TYPE] = @TYPE
                and [PrimaryFeild] = @SOURCE_PRIMARYKEY_NAME
                and [PRIMARYKEY] = @SOURCE_PRIMARYKEY_VALUE;
    end
END
--每次执行的都是单条语句
CREATE TRIGGER [dbo].[T_DAS_Tenant]  
ON [dbo].[DAS_Tenant]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN    
     --触发器对应的源表名
    DECLARE @SOURCE_TABLE_NAME nvarchar(200);
    SET @SOURCE_TABLE_NAME = '[SKEP_DAS].[DBO].[DAS_Tenant]';
    --源表中的主键名称
    DECLARE @SOURCE_PRIMARYKEY_NAME nvarchar(200);
    SET @SOURCE_PRIMARYKEY_NAME = 'TenantID';
    --源表中的主键的值
    DECLARE @SOURCE_PRIMARYKEY_VALUE nvarchar(200);
    SET @SOURCE_PRIMARYKEY_VALUE = '';
    --触发类型(1 新增、2 删除、3 修改)
    DECLARE @TYPE SMALLINT;
    
    IF(EXISTS(SELECT 1 FROM INSERTED) AND NOT EXISTS(SELECT 1 FROM DELETED))
    BEGIN 
        SET @TYPE = 1;
        SELECT TOP 1 @SOURCE_PRIMARYKEY_VALUE = TenantID FROM INSERTED;        
    END
    ELSE IF(NOT EXISTS(SELECT 1 FROM INSERTED) AND EXISTS(SELECT 1 FROM DELETED))
    BEGIN
        SET @TYPE = 2;
        SELECT TOP 1 @SOURCE_PRIMARYKEY_VALUE = TenantID FROM DELETED;
    END
    ELSE IF(EXISTS(SELECT 1 FROM INSERTED) AND EXISTS(SELECT 1 FROM DELETED))
    BEGIN
        SET @TYPE = 3;
        SELECT TOP 1 @SOURCE_PRIMARYKEY_VALUE = TenantID FROM INSERTED;        
    END
    
    EXEC DBO.P_TRIGGER_COMMON @SOURCE_TABLE_NAME, @SOURCE_PRIMARYKEY_NAME, @SOURCE_PRIMARYKEY_VALUE, @TYPE;
END
--每次执行的是多条语句时
CREATE TRIGGER [dbo].[T_DAS_StaffDetail]  
ON [dbo].[DAS_StaffDetail]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN    
     --触发器对应的源表名
    DECLARE @SOURCE_TABLE_NAME nvarchar(200);
    SET @SOURCE_TABLE_NAME = '[SKEP_DAS].[DBO].[DAS_StaffDetail]';
    --源表中的主键名称
    DECLARE @SOURCE_PRIMARYKEY_NAME nvarchar(200);
    SET @SOURCE_PRIMARYKEY_NAME = 'StaffCardID';
    --源表中的主键的值
    DECLARE @SOURCE_PRIMARYKEY_VALUE nvarchar(200);
    SET @SOURCE_PRIMARYKEY_VALUE = '';
    --触发类型(1 新增、2 删除、3 修改)
    DECLARE @TYPE SMALLINT;
    DECLARE @StaffID nvarchar(20);
    
    IF(EXISTS(SELECT 1 FROM INSERTED) AND NOT EXISTS(SELECT 1 FROM DELETED))
    BEGIN
        begin
            SELECT @StaffID = StaffID FROM INSERTED 
            if(@StaffID<>'0')
            begin 
                SET @TYPE = 1;
                
                declare MyCursor cursor for
                SELECT StaffCardID FROM INSERTED;
                
                open MyCursor;
                fetch next from MyCursor into @SOURCE_PRIMARYKEY_VALUE;
                while @@FETCH_STATUS = 0
                begin
                    EXEC DBO.P_TRIGGER_COMMON @SOURCE_TABLE_NAME, @SOURCE_PRIMARYKEY_NAME, @SOURCE_PRIMARYKEY_VALUE, @TYPE;
                    
                    fetch next from MyCursor into @SOURCE_PRIMARYKEY_VALUE;
                end
                
                close MyCursor;
                deallocate MyCursor;
            end
        end
    END
    ELSE IF(NOT EXISTS(SELECT 1 FROM INSERTED) AND EXISTS(SELECT 1 FROM DELETED))
    BEGIN
        begin
            SELECT @StaffID = StaffID FROM DELETED 
            if(@StaffID<>'0')
            begin 
                SET @TYPE = 2;
                
                declare MyCursor cursor for
                SELECT StaffCardID FROM INSERTED;
                
                open MyCursor;
                fetch next from MyCursor into @SOURCE_PRIMARYKEY_VALUE;
                while @@FETCH_STATUS = 0
                begin
                    EXEC DBO.P_TRIGGER_COMMON @SOURCE_TABLE_NAME, @SOURCE_PRIMARYKEY_NAME, @SOURCE_PRIMARYKEY_VALUE, @TYPE;
                    
                    fetch next from MyCursor into @SOURCE_PRIMARYKEY_VALUE;
                end
                
                close MyCursor;
                deallocate MyCursor;
            end
        end
    END
    ELSE IF(EXISTS(SELECT 1 FROM INSERTED) AND EXISTS(SELECT 1 FROM DELETED))
    BEGIN
        begin
            SELECT @StaffID = StaffID FROM INSERTED
            if(@StaffID<>'0')
            begin 
                SET @TYPE = 3;
                
                declare MyCursor cursor for
                SELECT StaffCardID FROM INSERTED;
                
                open MyCursor;
                fetch next from MyCursor into @SOURCE_PRIMARYKEY_VALUE;
                while @@FETCH_STATUS = 0
                begin
                    EXEC DBO.P_TRIGGER_COMMON @SOURCE_TABLE_NAME, @SOURCE_PRIMARYKEY_NAME, @SOURCE_PRIMARYKEY_VALUE, @TYPE;
                    
                    fetch next from MyCursor into @SOURCE_PRIMARYKEY_VALUE;
                end
                
                close MyCursor;
                deallocate MyCursor;
            end
        end
    END
END
GO
--目的表
CREATE TABLE [dbo].[HT_DEPT](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [DEPT_NO] [nvarchar](50) NOT NULL,
    [DEPT_NAME] [nvarchar](50) NOT NULL,
    [PARENT_NO] [nvarchar](30) NULL,
    [CREATE_TIME] [datetime] NULL,
    [MODIFY_TIME] [datetime] NULL,
    [DELETE_FLAG] [bit] NULL,
    [RECORD_COUNTER] [bigint] NULL,
 CONSTRAINT [PK_HT_DEPT] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

同步程序参考:

    public class SynchronizeSqlStr
    {
        public static readonly string TriggerTrackSql = "SELECT [TableName],[Type],[PrimaryKey] FROM [SKEP_DAS].[dbo].[SKEP_SYNC]";
        public static readonly string checkHasDataSql = "SELECT 1 FROM [HT_ACCESS].[dbo].[HT_PHOTO]";
        public static readonly string initDataSql = "";
        public static readonly string updateRecordCounterSql = "";
public static Dictionary<string, string> TableMappings = new Dictionary<string, string>(); public static readonly string InsertSQLs = @"INSERT INTO {0} ({1}) SELECT {2} FROM {3} WHERE {4} = '{5}';"; public static readonly string DeleteSQLs = "DELETE FROM {0} WHERE {1} = '{2}';"; public static readonly string UpdateSQLs = ""; // DeleteSQLs + "\r\n" + InsertSQLs; public static Dictionary<string, string[]> BaseParams = new Dictionary<string, string[]>(); static SynchronizeSqlStr() { int RecordDay = 1; int.TryParse(ConfigurationManager.AppSettings["RecordDay"].ToString(), out RecordDay); string getRecordDay = DateTime.Now.AddDays(-1 * RecordDay).ToShortDateString(); // 首次启动同步程序时,先初始化已有的数据 initDataSql = @"INSERT INTO [HT_ACCESS].[DBO].HT_PHOTO(EMP_NO,DEPT_NO,EMP_NAME,PHOTO_CONTENT,IS_QUALIFIED) SELECT StaffID,TenantID,StaffDisplayName,StaffPhotoImg,1 FROM [SKEP_DAS].[dbo].[DAS_Staff]; // RECORD_COUNTER 用于标记最新被修改过的数据,在初始化数据时由于 ID 是从1开始自增长的,所以可以用 ID 的值来初始 RECORD_COUNTER,(而首次用 "SELECT MAX(RECORD_COUNTER) + 1 FROM xxx" 插入的是 NULL) updateRecordCounterSql = @"UPDATE HT_ACCESS.DBO.HT_PHOTO SET RECORD_COUNTER = ID;"; // 门禁库表和中间库表的映射关系 TableMappings.Add("DAS_Staff", "HT_PHOTO"); BaseParams.Add("HT_PHOTO", new string[] { "[HT_ACCESS].[DBO].HT_PHOTO", "EMP_NO,DEPT_NO,EMP_NAME,PHOTO_CONTENT", "StaffID,TenantID,StaffDisplayName,StaffPhotoImg", "EMP_NO", "StaffID" }); } }
 
    public class HelloJob : IJob
    {
        private static LoggerAdapter<Program> logger = new LoggerAdapter<Program>();
        private static string _connStr = ConfigurationManager.ConnectionStrings["SQLConnectionStr"].ToString();
        string dtNow = "1900-01-01";
        private static readonly object _syncRoot = new object(); // 注意必须是 static 类型,否则每个实例都有自己的 _syncRoot,达不到全局互斥效果

        public virtual Task Execute(IJobExecutionContext context)
        {
            try
            {
                #region 初始化数据库表中的数据

                dtNow = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff");
                DataSet checkData = SQLHelper.Query(_connStr, SynchronizeSqlStr.checkHasDataSql);
                if (checkData == null || checkData.Tables == null || checkData.Tables.Count < 1 || checkData.Tables[0] == null || checkData.Tables[0].Rows.Count < 1)
                {
                    try
                    {
                        // 由于各主键ID是自增长的,因此为了简便,在初始化数据后,直接让 RECORD_COUNTER = ID(注意:updateRecordCounterSql 里排除里 RECORD 表,因为它只有一条记录)
                        string initDataSql = Convert.ToString(SynchronizeSqlStr.initDataSql + " " + SynchronizeSqlStr.updateRecordCounterSql);

                        // 这里不能用异步操作,因为后面的操作要在它执行成功后才能执行
                        SQLHelper.ExecuteNonQuery(_connStr, CommandType.Text, initDataSql);
                    }
                    catch (Exception e)
                    {
                        logger.LogError("初始化数据库表中的数据时出错:\r\n" + e.ToString() + " " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"));
                        return Task.FromResult(false);
                    }
                }

                #endregion
                
                #region 同步其他表的数据(根据门禁触发器的记录)

                // 获取门禁数据库服务器上的触发器生成的待同步的数据
                DataSet trackData = SQLHelper.Query(_connStr, "SELECT [TableName],[Type],[PrimaryKey] FROM [SKEP_DAS].[dbo].[SKEP_SYNC]";);

                if (trackData == null || trackData.Tables == null || trackData.Tables.Count < 1 || trackData.Tables[0] == null || trackData.Tables[0].Rows.Count < 1)                
                    return Task.FromResult(true);

                DataTable dt = trackData.Tables[0];
                int rowCount = trackData.Tables[0].Rows.Count;

                StringBuilder dataSyncSqls = new StringBuilder("");
                StringBuilder clearSqls = new StringBuilder("DELETE FROM [SKEP_DAS].[dbo].[SKEP_SYNC] WHERE [PrimaryKey] IN (");
                string clearSqlsFinal = "";
                string dataSyncSqlsAndclearSqlsFinal = "";

                //这样写有个问题,如果同一个表有多条记录,由于语句没有执行,则取maxrecordCount都是同一个值
                //改成每次拼接一条语句就执行一次
                for (int i = 0; i < rowCount; i++)
                {
                    try
                    {
                        // 提取表名(由于表名在各个数据库中都保持一样,这里提取表名后方便后面获取 Dictionary 中的参数)
                        string tableName = dt.Rows[i]["TableName"].ToString();
                        tableName = tableName.Substring(tableName.LastIndexOf('[') + 1).TrimEnd(new char[] { ']' });
                        tableName = SynchronizeSqlStr.TableMappings[tableName];

                        string[] tempParams = SynchronizeSqlStr.BaseParams[tableName];

                        string getMaxRecordCountSql = string.Format("SELECT MAX([RECORD_COUNTER]) + 1 FROM {0}", tempParams[0]);
                        object maxRecordCount = SQLHelper.ExecuteScalar(_connStr, CommandType.Text, getMaxRecordCountSql);
                        string maxCount = Convert.ToString(maxRecordCount);

                        if (string.IsNullOrWhiteSpace(maxCount))
                            maxCount = "1";

                        if (dt.Rows[i]["Type"].ToString() == "1") // 新增
                        {
                            dataSyncSqls.Append(
                                string.Format(SynchronizeSqlStr.InsertSQLs, tempParams[0], tempParams[1], tempParams[2], dt.Rows[i]["TableName"].ToString(), tempParams[4], dt.Rows[i]["PrimaryKey"].ToString())
                                    + "\r\n" + string.Format("UPDATE {0} SET [MODIFY_TIME] = '" + dtNow + "', [RECORD_COUNTER] = " + maxCount + " WHERE {1} = '{2}'", tempParams[0], tempParams[3], dt.Rows[i]["PrimaryKey"].ToString())
                                     + ";"
                                );
                        }
                        else if (dt.Rows[i]["Type"].ToString() == "2") // 删除(由于是软删除,所以和修改处理方式类似)
                        {
                            // dataSyncSqls.Append(string.Format(SynchronizeSqlStr.DeleteSQLs, tempParams[0], tempParams[3], dt.Rows[i]["PrimaryKey"].ToString()));
                            dataSyncSqls.Append(
                                    string.Format("UPDATE {0} SET [DELETE_FLAG] = 1, [MODIFY_TIME] = '" + dtNow + "', [RECORD_COUNTER] = " + maxCount + "  WHERE {1} = '{2}'", tempParams[0], tempParams[3], dt.Rows[i]["PrimaryKey"].ToString())
                                    + ";"
                                  );
                        }
                        else if (dt.Rows[i]["Type"].ToString() == "3") // 修改
                        {
                            // 注意:这里更新 RECORD_COUNTER 时不能用 MAX([RECORD_COUNTER]) +1,因为这里是先删除原记录(如果删除的记录的 RECORD_COUNTER 的值刚好最大,那么把 MAX([RECORD_COUNTER]) +1 也还是原来的值),然后复制新记录。
                            // 而利用 ID 自增长的特点,让 [RECORD_COUNTER] = [ID] 则刚好满足 [RECORD_COUNTER] 的要求
                            dataSyncSqls.Append(
                                string.Format(SynchronizeSqlStr.DeleteSQLs, tempParams[0], tempParams[3], dt.Rows[i]["PrimaryKey"].ToString())
                                + "\r\n" + string.Format(SynchronizeSqlStr.InsertSQLs, tempParams[0], tempParams[1], tempParams[2], dt.Rows[i]["TableName"].ToString(), tempParams[4], dt.Rows[i]["PrimaryKey"].ToString())
                                 + "\r\n" + string.Format("UPDATE {0} SET [MODIFY_TIME] = '" + dtNow + "', [RECORD_COUNTER] = " + maxCount + " WHERE {1} = '{2}'", tempParams[0], tempParams[3], dt.Rows[i]["PrimaryKey"].ToString())
                                   + ";"
                                );
                        }

                        SQLHelper.ExecuteNonQuery(_connStr, CommandType.Text, dataSyncSqls.ToString());

                        // 清理中间表里的数据
                        clearSqls.Append("'" + dt.Rows[i]["PrimaryKey"] + "',");
                    }
                    catch (Exception e)
                    {
                        logger.LogError("拼接脚本时出错:\r\n" + e.ToString() + " " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff") + "\r\n" + dataSyncSqls.ToString());
                        break;
                    }

                    // 拼接完脚本后即可删除[SKEP_DAS].[dbo].[SKEP_SYNC]表中的临时数据
                    clearSqlsFinal = clearSqls.ToString().TrimEnd(new char[] { ',' }) + ");";
                    // 把同步脚本和清理脚本合并在一起执行
                    dataSyncSqlsAndclearSqlsFinal = clearSqlsFinal;
                }

                // 执行拼接好的脚本
                Thread thread = new Thread(SynchronizeProcess);
                thread.IsBackground = true;
                thread.Start(dataSyncSqlsAndclearSqlsFinal);

                #endregion

                return Task.FromResult(true);
            }

            catch (Exception ex)
            {
                logger.LogError("执行同步程序时出错:\r\n" + ex.ToString() + " " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"));
                return Task.FromResult(false);
            }
        }

        // 由于现场数据量较大,一次执行所有语句会导致程序等待超时,事务执行失败
        // 这里改成多线程分别去操作不同的表
        private void SynchronizeProcess(object sqlStr)
        {
            try
            {
                string sql = Convert.ToString(sqlStr);
                SQLHelper.ExecuteNonQuery(_connStr, CommandType.Text, sql);
            }
            catch (Exception ex)
            {
                logger.LogError("执行事务脚本的线程出现异常:\r\n" + ex.ToString() + " " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"));
            }
        }
    }
 

猜你喜欢

转载自www.cnblogs.com/hellowzl/p/11121512.html