43

Entry次のように宣言 されたという名前のクラスがあります。

class Entry{
    string Id {get;set;}
    string Name {get;set;}
}  

Entry次に、 ADO.NET を使用してデータベースに挿入する 複数のそのようなオブジェクトを受け入れるメソッド:

static void InsertEntries(IEnumerable<Entry> entries){
    //build a SqlCommand object
    using(SqlCommand cmd = new SqlCommand()){
        ...
        const string refcmdText = "INSERT INTO Entries (id, name) VALUES (@id{0},@name{0});";
        int count = 0;
        string query = string.Empty;
        //build a large query
        foreach(var entry in entries){
            query += string.Format(refcmdText, count);
            cmd.Parameters.AddWithValue(string.Format("@id{0}",count), entry.Id);
            cmd.Parameters.AddWithValue(string.Format("@name{0}",count), entry.Name);
            count++;
        }
        cmd.CommandText=query;
        //and then execute the command
        ...
    }
}  

そして、私の質問は次のとおりです。複数の挿入ステートメントを送信する上記の方法を使用し続ける必要がありますか(挿入ステートメントとそのパラメーターの巨大な文字列を作成し、ネットワーク経由で送信します)、または接続を開いたままにして単一の挿入ステートメントを送信する必要がありますそれぞれEntry次のように:

using(SqlCommand cmd = new SqlCommand(){
    using(SqlConnection conn = new SqlConnection(){
        //assign connection string and open connection
        ...
        cmd.Connection = conn;
        foreach(var entry in entries){
            cmd.CommandText= "INSERT INTO Entries (id, name) VALUES (@id,@name);";
            cmd.Parameters.AddWithValue("@id", entry.Id);
            cmd.Parameters.AddWithValue("@name", entry.Name);
            cmd.ExecuteNonQuery();
        }
    }
 }  

どう思いますか?2 つの SQL Server のパフォーマンスに違いはありますか? 他に注意すべき影響はありますか?

4

10 に答える 10

66
static void InsertSettings(IEnumerable<Entry> settings) {
    using (SqlConnection oConnection = new SqlConnection("Data Source=(local);Initial Catalog=Wip;Integrated Security=True")) {
        oConnection.Open();
        using (SqlTransaction oTransaction = oConnection.BeginTransaction()) {
            using (SqlCommand oCommand = oConnection.CreateCommand()) {
                oCommand.Transaction = oTransaction;
                oCommand.CommandType = CommandType.Text;
                oCommand.CommandText = "INSERT INTO [Setting] ([Key], [Value]) VALUES (@key, @value);";
                oCommand.Parameters.Add(new SqlParameter("@key", SqlDbType.NChar));
                oCommand.Parameters.Add(new SqlParameter("@value", SqlDbType.NChar));
                try {
                    foreach (var oSetting in settings) {
                        oCommand.Parameters[0].Value = oSetting.Key;
                        oCommand.Parameters[1].Value = oSetting.Value;
                        if (oCommand.ExecuteNonQuery() != 1) {
                            //'handled as needed, 
                            //' but this snippet will throw an exception to force a rollback
                            throw new InvalidProgramException();
                        }
                    }
                    oTransaction.Commit();
                } catch (Exception) {
                    oTransaction.Rollback();
                    throw;
                }
            }
        }
    }
}
于 2010-06-05T07:53:23.527 に答える
28

もし私があなたなら、どちらも使わないでしょう。

最初のものの欠点は、リストに同じ値がある場合にパラメーター名が衝突する可能性があることです。

2つ目の欠点は、エンティティごとにコマンドとパラメーターを作成していることです。

最良の方法は、コマンドテキストとパラメーターを一度作成して(Parameters.Addパラメーターの追加に使用)、ループ内の値を変更してコマンドを実行することです。そうすれば、ステートメントは1回だけ作成されます。また、ループを開始する前に接続を開き、ループの後で接続を閉じる必要があります。

于 2010-06-04T10:02:25.780 に答える
20

これを行うための本当にひどい方法は、各INSERTステートメントを独自のバッチとして実行することです。

バッチ 1:

INSERT INTO Entries (id, name) VALUES (1, 'Ian Boyd);

バッチ 2:

INSERT INTO Entries (id, name) VALUES (2, 'Bottlenecked);

バッチ 3:

INSERT INTO Entries (id, name) VALUES (3, 'Marek Grzenkowicz);

バッチ 4:

INSERT INTO Entries (id, name) VALUES (4, 'Giorgi);

バッチ 5:

INSERT INTO Entries (id, name) VALUES (5, 'AMissico);

注:パラメータ化、エラー チェック、および説明目的で除外されたその他のニッチ ピック。

これは本当に、恐ろしい、恐ろしい方法です。毎回ネットワークの往復時間がかかるため、本当にひどいパフォーマンスになります

はるかに優れた解決策は、すべてのINSERTステートメントを 1 つのバッチにまとめることです。

バッチ 1:

INSERT INTO Entries (id, name) VALUES (1, 'Ian Boyd');
INSERT INTO Entries (id, name) VALUES (2, 'Bottlenecked');
INSERT INTO Entries (id, name) VALUES (3, 'Marek Grzenkowicz');
INSERT INTO Entries (id, name) VALUES (4, 'Giorgi');
INSERT INTO Entries (id, name) VALUES (5, 'AMissico');

この方法では、1 往復だけで済みます。このバージョンでは、パフォーマンスが大幅に向上します。約 5 倍高速です。

さらに良いのは、次のVALUES句を使用することです。

INSERT INTO Entries (id, name)
VALUES 
(1, 'Ian Boyd'),
(2, 'Bottlenecked'),
(3, 'Marek Grzenkowicz'),
(4, 'Giorgi'),
(5, 'AMissico');

INSERTこれにより、5 つの個別の s バージョンよりもパフォーマンスが向上します。これにより、サーバーは得意なことを実行できます:セットでの操作:

  • 各トリガーは一度だけ操作する必要があります
  • 外部キーは一度チェックされます
  • 一意の制約は一度チェックされます

SQL Server は一連のデータを操作するのが大好きです。それはバイキングです!

パラメータ制限

上記の T-SQL の例では、わかりやすくするためにすべてのパラメーター化が削除されています。しかし実際には、クエリをパラメータ化する必要があります

  • サーバーが各 T-SQL バッチをコンパイルする必要がなくなるというパフォーマンスのボーナスはそれほど大きくありません (ただし、高速な一括インポート中は、解析時間の節約が実際に加算される可能性があります)。
  • SQL インジェクションを避けるほどではありません。あなたはすでに優れた開発者であり、QuotedString(firstName)
  • ただし、サーバーのクエリ プラン キャッシュがギガバイト単位のアドホック クエリ プランであふれないようにするためです。(私は、SQL Server のワーキング セット、つまり RAM が 2 GB のパラメーター化されていない SQL クエリ プランであることを確認しました)

しかし、ブルーノには重要な点があります。SQL Server のドライバーでは、バッチに 2,100 個のパラメーターしか含めることができません。上記のクエリには 2 つの値があります。

@id、@name

1 回のバッチで 1,051 行をインポートすると、2,102 個のパラメーターになります。次のエラーが表示されます。

この RPC リクエストで指定されたパラメータが多すぎます

そのため、一度に 5 行または 10 行を挿入するのが一般的です。バッチごとに行を追加しても、パフォーマンスはそれほど向上しません。利益は減少します。

パラメーターの数を少なく保ち、T-SQL バッチ サイズの制限に近づくことはありません。VALUESいずれにせよ、句は 1000 タプルに制限されているという事実もあります。

実装する

最初のアプローチは良いですが、次の問題があります。

  • パラメータ名の衝突
  • 無制限の行数 (2100 パラメーターの制限に達する可能性があります)

したがって、目標は次のような文字列を生成することです。

INSERT INTO Entries (id, name) VALUES
(@p1, @p2),
(@p3, @p4),
(@p5, @p6),
(@p7, @p8),
(@p9, @p10)

私は私のズボンの席であなたのコードを変更します

IEnumerable<Entry> entries = GetStuffToInsert();

SqlCommand cmd = new SqlCommand();
StringBuilder sql = new StringBuilder();
Int32 batchSize = 0; //how many rows we have build up so far
Int32 p = 1; //the current paramter name (i.e. "@p1") we're going to use

foreach(var entry in entries)
{
   //Build the names of the parameters
   String pId =   String.Format("@p{0}", p);   //the "Id" parameter name (i.e. "p1")
   String pName = String.Format("@p{0}", p+1); //the "Name" parameter name (i.e. "p2")
   p += 2;

   //Build a single "(p1, p2)" row
   String row = String.Format("({0}, {1})", pId, pName); //a single values tuple

   //Add the row to our running SQL batch
   if (batchSize > 0)
      sb.AppendLine(",");
   sb.Append(row);
   batchSize += 1;

   //Add the parameter values for this row
   cmd.Parameters.Add(pID,   System.Data.SqlDbType.Int   ).Value = entry.Id;
   cmd.Parameters.Add(pName, System.Data.SqlDbType.String).Value = entry.Name;

   if (batchSize >= 5)
   {
       String sql = "INSERT INTO Entries (id, name) VALUES"+"\r\n"+
                    sb.ToString();
       cmd.CommandText = sql;
       cmd.ExecuteNonQuery();
       cmd.Parameters.Clear();
       sb.Clear();
       batchSize = 0;
       p = 1;
   }
}

//handle the last few stragglers
if (batchSize > 0)
{
    String sql = "INSERT INTO Entries (id, name) VALUES"+"\r\n"+
                 sb.ToString();
    cmd.CommandText = sql;
    cmd.ExecuteNonQuery();
}
于 2020-05-05T23:26:04.393 に答える
10

巨大なコマンド Text を作成する代わりに、すべてのループでコマンドを実行する必要があります (ところで、StringBuilderはこのために作成されています) 基礎となる接続はループごとに閉じられず、再度開かれません。接続プール マネージャーがこれを処理します。詳細については、このリンクを参照してください: ASP.NET アプリケーションでの ADO.NET 接続プールの調整

すべてのコマンドが正常に実行されるようにしたい場合は、必要に応じてトランザクションとロールバックを使用できます。

于 2010-06-04T09:59:53.107 に答える
2

エントリが多い場合は、SqlBulkCopyの使用を検討してください。パフォーマンスは、一連のシングルインサートよりもはるかに高速です。

于 2010-06-05T08:07:41.090 に答える
0

DataTable正しく作成されていれば、直接挿入できます。

最初に、アクセス テーブルの列の列名が同じで、型が類似していることを確認します。次に、非常に高速でエレガントであると私が信じているこの関数を使用できます。

public void AccessBulkCopy(DataTable table)
{
    foreach (DataRow r in table.Rows)
        r.SetAdded();

    var myAdapter = new OleDbDataAdapter("SELECT * FROM " + table.TableName, _myAccessConn);

    var cbr = new OleDbCommandBuilder(myAdapter);
    cbr.QuotePrefix = "[";
    cbr.QuoteSuffix = "]";
    cbr.GetInsertCommand(true);

    myAdapter.Update(table);
}
于 2016-09-06T21:42:19.097 に答える
-3

単一の挿入を使用して複数のレコードを挿入するストアド プロシージャ:

ALTER PROCEDURE [dbo].[Ins]
@i varchar(50),
@n varchar(50),
@a varchar(50),
@i1 varchar(50),
@n1 varchar(50),
@a1 varchar(50),
@i2 varchar(50),
@n2 varchar(50),
@a2 varchar(50) 
AS
INSERT INTO t1
SELECT     @i AS Expr1, @i1 AS Expr2, @i2 AS Expr3
UNION ALL
SELECT     @n AS Expr1, @n1 AS Expr2, @n2 AS Expr3
UNION ALL
SELECT     @a AS Expr1, @a1 AS Expr2, @a2 AS Expr3
RETURN

コードビハインド:

protected void Button1_Click(object sender, EventArgs e)
{
    cn.Open();
    SqlCommand cmd = new SqlCommand("Ins",cn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.AddWithValue("@i",TextBox1.Text);
    cmd.Parameters.AddWithValue("@n",TextBox2.Text);
    cmd.Parameters.AddWithValue("@a",TextBox3.Text);
    cmd.Parameters.AddWithValue("@i1",TextBox4.Text);
    cmd.Parameters.AddWithValue("@n1",TextBox5.Text);
    cmd.Parameters.AddWithValue("@a1",TextBox6.Text);
    cmd.Parameters.AddWithValue("@i2",TextBox7.Text);
    cmd.Parameters.AddWithValue("@n2",TextBox8.Text);
    cmd.Parameters.AddWithValue("@a2",TextBox9.Text);
    cmd.ExecuteNonQuery();
    cn.Close();
    Response.Write("inserted");
    clear();
}
于 2011-12-02T05:39:40.290 に答える
-5
ClsConectaBanco bd = new ClsConectaBanco();

StringBuilder sb = new StringBuilder();
sb.Append("  INSERT INTO FAT_BALANCETE ");
sb.Append(" ([DT_LANCAMENTO]           ");
sb.Append(" ,[ID_LANCAMENTO_CONTABIL]  ");
sb.Append(" ,[NR_DOC_CONTABIL]         ");
sb.Append(" ,[TP_LANCAMENTO_GERADO]    ");
sb.Append(" ,[VL_LANCAMENTO]           ");
sb.Append(" ,[TP_NATUREZA]             ");
sb.Append(" ,[CD_EMPRESA]              ");
sb.Append(" ,[CD_FILIAL]               ");
sb.Append(" ,[CD_CONTA_CONTABIL]       ");
sb.Append(" ,[DS_CONTA_CONTABIL]       ");
sb.Append(" ,[ID_CONTA_CONTABIL]       ");
sb.Append(" ,[DS_TRIMESTRE]            ");
sb.Append(" ,[DS_SEMESTRE]             ");
sb.Append(" ,[NR_TRIMESTRE]            ");
sb.Append(" ,[NR_SEMESTRE]             ");
sb.Append(" ,[NR_ANO]                  ");
sb.Append(" ,[NR_MES]                  ");
sb.Append(" ,[NM_FILIAL])              ");
sb.Append(" VALUES                     ");
sb.Append(" (@DT_LANCAMENTO            ");
sb.Append(" ,@ID_LANCAMENTO_CONTABIL   ");
sb.Append(" ,@NR_DOC_CONTABIL          ");
sb.Append(" ,@TP_LANCAMENTO_GERADO     ");
sb.Append(" ,@VL_LANCAMENTO            ");
sb.Append(" ,@TP_NATUREZA              ");
sb.Append(" ,@CD_EMPRESA               ");
sb.Append(" ,@CD_FILIAL                ");
sb.Append(" ,@CD_CONTA_CONTABIL        ");
sb.Append(" ,@DS_CONTA_CONTABIL        ");
sb.Append(" ,@ID_CONTA_CONTABIL        ");
sb.Append(" ,@DS_TRIMESTRE             ");
sb.Append(" ,@DS_SEMESTRE              ");
sb.Append(" ,@NR_TRIMESTRE             ");
sb.Append(" ,@NR_SEMESTRE              ");
sb.Append(" ,@NR_ANO                   ");
sb.Append(" ,@NR_MES                   ");
sb.Append(" ,@NM_FILIAL)               ");

SqlCommand cmd = new SqlCommand(sb.ToString(), bd.CriaConexaoSQL());
bd.AbrirConexao();

cmd.Parameters.Add("@DT_LANCAMENTO", SqlDbType.Date);
cmd.Parameters.Add("@ID_LANCAMENTO_CONTABIL", SqlDbType.Int);
cmd.Parameters.Add("@NR_DOC_CONTABIL", SqlDbType.VarChar,255);
cmd.Parameters.Add("@TP_LANCAMENTO_GERADO", SqlDbType.VarChar,255);
cmd.Parameters.Add("@VL_LANCAMENTO", SqlDbType.Decimal);
cmd.Parameters["@VL_LANCAMENTO"].Precision = 15;
cmd.Parameters["@VL_LANCAMENTO"].Scale = 2;
cmd.Parameters.Add("@TP_NATUREZA", SqlDbType.VarChar, 1);
cmd.Parameters.Add("@CD_EMPRESA",SqlDbType.Int);
cmd.Parameters.Add("@CD_FILIAL", SqlDbType.Int);
cmd.Parameters.Add("@CD_CONTA_CONTABIL", SqlDbType.VarChar, 255);
cmd.Parameters.Add("@DS_CONTA_CONTABIL", SqlDbType.VarChar, 255);
cmd.Parameters.Add("@ID_CONTA_CONTABIL", SqlDbType.VarChar,50);
cmd.Parameters.Add("@DS_TRIMESTRE", SqlDbType.VarChar, 4);
cmd.Parameters.Add("@DS_SEMESTRE", SqlDbType.VarChar, 4);
cmd.Parameters.Add("@NR_TRIMESTRE", SqlDbType.Int);
cmd.Parameters.Add("@NR_SEMESTRE", SqlDbType.Int);
cmd.Parameters.Add("@NR_ANO", SqlDbType.Int);
cmd.Parameters.Add("@NR_MES", SqlDbType.Int);
cmd.Parameters.Add("@NM_FILIAL", SqlDbType.VarChar, 255);
cmd.Prepare();

 foreach (dtoVisaoBenner obj in lista)
 {
     cmd.Parameters["@DT_LANCAMENTO"].Value = obj.CTLDATA;
     cmd.Parameters["@ID_LANCAMENTO_CONTABIL"].Value = obj.CTLHANDLE.ToString();
     cmd.Parameters["@NR_DOC_CONTABIL"].Value = obj.CTLDOCTO.ToString();
     cmd.Parameters["@TP_LANCAMENTO_GERADO"].Value = obj.LANCAMENTOGERADO;
     cmd.Parameters["@VL_LANCAMENTO"].Value = obj.CTLANVALORF;
     cmd.Parameters["@TP_NATUREZA"].Value = obj.NATUREZA;
     cmd.Parameters["@CD_EMPRESA"].Value = obj.EMPRESA;
     cmd.Parameters["@CD_FILIAL"].Value = obj.FILIAL;
     cmd.Parameters["@CD_CONTA_CONTABIL"].Value = obj.CONTAHANDLE.ToString();
     cmd.Parameters["@DS_CONTA_CONTABIL"].Value = obj.CONTANOME.ToString();
     cmd.Parameters["@ID_CONTA_CONTABIL"].Value = obj.CONTA;
     cmd.Parameters["@DS_TRIMESTRE"].Value = obj.TRIMESTRE;
     cmd.Parameters["@DS_SEMESTRE"].Value = obj.SEMESTRE;
     cmd.Parameters["@NR_TRIMESTRE"].Value = obj.NRTRIMESTRE;
     cmd.Parameters["@NR_SEMESTRE"].Value = obj.NRSEMESTRE;
     cmd.Parameters["@NR_ANO"].Value = obj.NRANO;
     cmd.Parameters["@NR_MES"].Value = obj.NRMES;
     cmd.Parameters["@NM_FILIAL"].Value = obj.NOME;
     cmd.ExecuteNonQuery();
     rowAffected++;
 }
于 2012-09-12T14:08:02.410 に答える