【问题标题】:Optimize insert procedure (my procedure takes 1h30 to insert 100'000 rows)优化插入过程(我的过程需要 1h30 插入 100'000 行)
【发布时间】:2012-05-04 00:38:43
【问题描述】:

我有一个将数据插入 Oracle 数据库的 c# 过程。我尝试使用批量插入,但性能非常糟糕,我不知道为什么。插入超过 100,000 行大约需要 1h30!

这是我的插入过程:

internal void InsertPublications(DateTime aDate, List<Model.Publication> aPublications, Action<int> aProgress, Action<int> aCallBack)
{
    List<List<Model.Publication>> _Publications = Split(aPublications, 128);

    int _InsertedCount = 0;

    var _Worker = new BackgroundWorker() { WorkerReportsProgress = true };

    _Worker.DoWork += (_Sender, _Args) =>
    {
        var           _TableName = string.Format("SRC_PUBLICATION_{0}", aDate.ToString("yyyyMMdd"));
        OracleCommand _Command;

        this.Connect();

        try
        {
            this.CreateOrReplaceTable(_TableName, SQL_CREATE_PULICATIONS_TABLE);

            foreach (var _PublicationSub in _Publications)
            {
                if (!DlgParamPublication.alive)
                {
                    break;
                }

                _Command = this.Connection.CreateCommand();
                _Command.CommandText = string.Format(SQL_INSERT_PULICATIONS, _TableName);
                _Command.CommandType = CommandType.Text;
                _Command.BindByName = true;
                _Command.ArrayBindCount = _PublicationSub.Count;

                _Command.Parameters.Add(":ANNEE", OracleDbType.NVarchar2, _PublicationSub.Select(_Item => _Item.Year).ToArray(), ParameterDirection.Input);
                _Command.Parameters.Add(":IDE_PUBLICATION", OracleDbType.NVarchar2, _PublicationSub.Select(_Item => _Item.Pid).ToArray(), ParameterDirection.Input);
                _Command.Parameters.Add(":IDE_TYPE_PUBLICATION", OracleDbType.NVarchar2, _PublicationSub.Select(_Item => _Item.IdePublicationType.ToString().ToLower()).ToArray(), ParameterDirection.Input);
                _Command.Parameters.Add(":IDE_TYPE_TRAVAIL", OracleDbType.NVarchar2, _PublicationSub.Select(_Item => _Item.IdeTypeOfWork.ToString().ToLower()).ToArray(), ParameterDirection.Input);
                _Command.Parameters.Add(":IDE_NIVEAU_ELEMENT_ORG", OracleDbType.NVarchar2, _PublicationSub.Select(_Item => _Item.UserRefType.ToString().ToLower()).ToArray(), ParameterDirection.Input);
                _Command.Parameters.Add(":IDE_ELEMENT_ORG", OracleDbType.NVarchar2, _PublicationSub.Select(_Item => _Item.UserRefValue).ToArray(), ParameterDirection.Input);
                _Command.Parameters.Add(":AUTEUR", OracleDbType.NClob, _PublicationSub.Select(_Item => _Item.Author).ToArray(), ParameterDirection.Input);

                _InsertedCount += _Command.ExecuteNonQuery();

                _Worker.ReportProgress(0, _InsertedCount);
            }
        catch (Exception _Exc)
        {
            ViewModel.DlgParamPublication.Logger.LogException("INSERT PUBLICATIONS", _Exc, false);
        }
        finally
        {
            this.Disconnect();
        }
    };

    _Worker.ProgressChanged    += (_Sender, _Args) => { aProgress((int)_Args.UserState); };
    _Worker.RunWorkerCompleted += (_Sender, _Args) => { aCallBack(_InsertedCount); };

    // Start background thread
    if (DlgParamPublication.alive)
    {
        _Worker.RunWorkerAsync();
    }
}

知道为什么要花这么长时间吗?

更新

有人有其他建议吗?我无法解决问题。

【问题讨论】:

标签: c# oracle bulkinsert


【解决方案1】:

我将尝试在 foreach 循环之外创建 OracleCommand 和所有 OracleParameter。
在循环内部,我将添加当前迭代的值

_Command = this.Connection.CreateCommand();             
_Command.CommandText = string.Format(SQL_INSERT_PULICATIONS, _TableName);             _Command.CommandType = CommandType.Text;             
_Command.BindByName = true;             
_Command.Parameters.Add(":ANNEE", OracleDbType.NVarchar2, "", ParameterDirection.Input);             _Command.Parameters.Add(":IDE_PUBLICATION", OracleDbType.NVarchar2, "", ParameterDirection.Input);             
.....

foreach (var _PublicationSub in _Publications)             
{ 
     _Command.Parameters[":ANNEE"].Value = _PublicationSub.Select(_Item => _Item.Year).ToArray();
     ....
}

【讨论】:

  • @krlzlx 那么您可以尝试其他技巧,例如禁用索引,但考虑到数据量,您应该尝试使用批量复制see this question
【解决方案2】:

这个想法是在 single 存储的过程调用中为每个参数发送值数组。您实际上是在发送单个值的数组 n 次,其中 n_Publications 的长度。

删除外部循环并将您的_PublicationSub.Select 替换为_Publications.Select

您还需要更改绑定计数_Command.ArrayBindCount = _Publications.Count;


为了方便起见,我认为这是可行的:

try
        {
            this.CreateOrReplaceTable(_TableName, SQL_CREATE_PULICATIONS_TABLE);


            if (!DlgParamPublication.alive)
            {
                break;
            }

            _Command = this.Connection.CreateCommand();
            _Command.CommandText = string.Format(SQL_INSERT_PULICATIONS, _TableName);
            _Command.CommandType = CommandType.Text;
            _Command.BindByName = true;
            _Command.ArrayBindCount = _Publications.Count;

            _Command.Parameters.Add(":ANNEE", OracleDbType.NVarchar2, _Publications.Select(_Item => _Item.Year).ToArray(), ParameterDirection.Input);
            _Command.Parameters.Add(":IDE_PUBLICATION", OracleDbType.NVarchar2, _Publications.Select(_Item => _Item.Pid).ToArray(), ParameterDirection.Input);
            _Command.Parameters.Add(":IDE_TYPE_PUBLICATION", OracleDbType.NVarchar2, _Publications.Select(_Item => _Item.IdePublicationType.ToString().ToLower()).ToArray(), ParameterDirection.Input);
            _Command.Parameters.Add(":IDE_TYPE_TRAVAIL", OracleDbType.NVarchar2, _Publications.Select(_Item => _Item.IdeTypeOfWork.ToString().ToLower()).ToArray(), ParameterDirection.Input);
            _Command.Parameters.Add(":IDE_NIVEAU_ELEMENT_ORG", OracleDbType.NVarchar2, _Publications.Select(_Item => _Item.UserRefType.ToString().ToLower()).ToArray(), ParameterDirection.Input);
            _Command.Parameters.Add(":IDE_ELEMENT_ORG", OracleDbType.NVarchar2, _Publications.Select(_Item => _Item.UserRefValue).ToArray(), ParameterDirection.Input);
            _Command.Parameters.Add(":AUTEUR", OracleDbType.NClob, _Publications.Select(_Item => _Item.Author).ToArray(), ParameterDirection.Input);

            _InsertedCount += _Command.ExecuteNonQuery();

            _Worker.ReportProgress(0, _InsertedCount);
        }
    catch (Exception _Exc)
    {
        ViewModel.DlgParamPublication.Logger.LogException("INSERT PUBLICATIONS", _Exc, false);
    }
    finally
    {
        this.Disconnect();
    }

您可能希望删除后台工作人员的进度,因为这是一次性的,0-100% 在一次调用中处理。

【讨论】:

  • “移除外循环”是什么意思?这一行: foreach (var _PublicationSub in _Publications) ?
  • @krlzlx 检查编辑。我正在有效地将我的指示应用到您发布的代码中。
  • 我尝试了您的解决方案。似乎不起作用。没有任何东西添加到数据库中。跟后台worker有关系吗?
  • 我试图删除后台工作人员。应用程序在 3 分钟后冻结,并且没有向数据库添加任何内容。
  • 它似乎冻结了,因为您正在 UI 线程上执行所有操作。这实际上取决于您要添加的项目有多大(当转换为字符串并作为参数发送时)。一次批处理 50k 对您来说可能是有意义的。
【解决方案3】:

我不确定,但问题可能是 BindByName。我建议像this example 那样进行数字绑定。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-11
    • 1970-01-01
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 2015-08-28
    • 1970-01-01
    相关资源
    最近更新 更多