.NET を使って SQL Server に対して SELECT するには、ってことでパフォーマンスのチェックをしました。
- 10 個のテーブルに 20000 件ずつデータを入れておきます。
- 10 個のテーブルに対して、2000 回ずつ検索します。
- テーブルには主キーを付けておきます。
という形でデータを作っておいて、以下のパターンで検索をします。
- 個別に SqlDataAdapter を使って呼び出し
- 10 個のテーブルの検索をひとつにまとめて、SqlDataAdapter で呼び出し
- 10 個のテーブルの検索をひとつにまとめて、SqlCommand で呼び出し
- LINQ to Entities を使って呼び出し
- LINQ to SQL を使って呼び出し
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 1.個別SELECT 56.5 59.3 57.5 2.まとめてSELECT ※ 1 より遅くなったのでパス 3.SqlCommand 7.6 7.5 7.4 4.LINQ to Entities 112.5 110.6 108.2 5.LINQ to SQL 92.4 112.3 93.9 |
結論から言えば、
- 個別SELECTよりも、LINQ を使うと 2 倍ぐらい遅くなる。
- LINQ to Entities と LINQ to SQL はスピードが変わらない。
- SqlCommand を使うと、個別SELECTよりも 5 倍以上、LINQ よりも 10 倍以上早い。
という結果になりました。
LINQ の場合、10 個のテーブルに対して、検索を実行するために 10 回呼び出してしまうので、この手の方法は不利なのですが、SqlDataAdapter よりも遅いのは意外です…スピードが要求されるところで、LINQ を使うのは禁物ですね。
パフォーマンスのチューニングとしては、SqlCommand が一番やりやすく、10 個のテーブルに対する SELECT を 1 回で済ませられるのも SQL Server で複数の検索結果(DataTable)を返せるからです。
また、prepared statements という形で、あらかじめ SQL 文を発行させて再利用できるとろも SqlCommand の有利なところですね。
実験ソースを晒しておきます。
■テーブルの作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /// <summary> /// テーブル作成 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click( object sender, EventArgs e) { string sql = "" ; SqlConnection cn = new SqlConnection(CNSTR); cn.Open(); SqlCommand cmd; for ( int i = 0; i < 10; i++) { sql = string .Format( "drop table table{0}" , i); cmd = new SqlCommand(sql, cn); try { cmd.ExecuteNonQuery(); } catch { } sql = string .Format( "create table table{0} (" , i); for ( int j = 0; j < 10; j++) { if (j == 0) { sql += string .Format( "col{0} varchar(50) not null," , j); } else { sql += string .Format( "col{0} varchar(50)," , j); } } sql += string .Format( " CONSTRAINT pk_table{0} PRIMARY KEY ( col0 ) " , i ); sql += ")" ; cmd = new SqlCommand(sql, cn); cmd.ExecuteNonQuery(); } cn.Close(); } |
■20000件のデータ insert
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | /// <summary> /// データ作成 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button2_Click( object sender, EventArgs e) { int max = int .Parse(textBox1.Text); SqlConnection cn = new SqlConnection(CNSTR); cn.Open(); string s = toMD5(DateTime.Now.ToString()); int count = 0; for ( int i = 0; i < 10; i++) { string sql = string .Format( "insert into table{0} values (" , i); SqlCommand cmd = new SqlCommand( "" , cn); for ( int k = 0; k < 10; k++) { cmd.Parameters.Add( new SqlParameter( string .Format( "@col{0}" , k), SqlDbType.VarChar, 50)); sql += string .Format( "@col{0}," , k); } sql = sql.Substring(0, sql.Length - 1); sql += ")" ; cmd.CommandText = sql; for ( int j = 0; j < max; j++) { for ( int k = 0; k < 10; k++) { s = toMD5(s); cmd.Parameters[k].Value = s; } cmd.ExecuteNonQuery(); count++; if (count % 100 == 0) { toStatus(count, max * 10); } } } cn.Close(); } |
■個別SELECT
まずいやり方ですが、string.Format で値込みで文字列を作っています。
よくあるパターンですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /// <summary> /// 個別SELECT /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button3_Click( object sender, EventArgs e) { // 律儀にSELECT文を10回発行します。 DateTime start = DateTime.Now; string s = toMD5(start.ToString()); int count = 0; SqlConnection cn = new SqlConnection(CNSTR); int max = 2000; for ( int i = 0; i < max; i++) { for ( int j=0; j<10; j++ ) { DataTable dt = new DataTable(); SqlDataAdapter da = new SqlDataAdapter( string .Format( "SELECT * FROM table{0} WHERE col0 = '{1}'" , j, s), cn); s = toMD5(s); da.Fill( dt ); } if (++count % 10 == 0) toStatus(count, max); } DateTime end = DateTime.Now; textBox2.Text = ((TimeSpan)(end - start)).TotalSeconds.ToString( "#.0" ); } |
■まとめてSELECT
SqlDataAdapter に渡す SQL 文をひとまとめにしたのですが、全然駄目です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /// <summary> /// まとめてSELECT /// </summary> /// <param name="sender"></param> /// <param name="e"></param> /// <remarks> /// 遅くてぜんぜんだめ /// </remarks> private void button4_Click( object sender, EventArgs e) { // 10回のSELECTをひとまとめにして DataSet に保存します。 DateTime start = DateTime.Now; string s = toMD5(start.ToString()); int count = 0; SqlConnection cn = new SqlConnection(CNSTR); string sql = "" ; int max = 2000; for ( int i = 0; i < max; i++) { for ( int j = 0; j < 10; j++) { sql += string .Format( "SELECT * FROM table{0} WHERE col0 = '{1}' " , j, s); s = toMD5(s); } DataSet ds = new DataSet(); SqlDataAdapter da = new SqlDataAdapter(sql, cn); da.Fill(ds); if (++count % 10 == 0) toStatus(count, max ); } DateTime end = DateTime.Now; textBox2.Text = ((TimeSpan)(end - start)).TotalSeconds.ToString( "#.0" ); } |
■SqlCommand を使う
あらかじめコンパイルされるように SqlCommand を使います。
ループの中で SqlCommand を使ってはいけません。ループの外で作成して、パラメータで渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /// <summary> /// SqlCommand の利用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button5_Click( object sender, EventArgs e) { // 10回のSELECTをひとまとめにして DataSet に保存します。 DateTime start = DateTime.Now; string s = toMD5(start.ToString()); int count = 0; SqlConnection cn = new SqlConnection(CNSTR); string sql = "" ; </p> <p> SqlCommand cmd = new SqlCommand( "" ,cn); for ( int j = 0; j < 10; j++) { sql += string .Format( "SELECT * FROM table{0} WHERE col0 = @param{1} " , j, j); s = toMD5(s); cmd.Parameters.Add( new SqlParameter( string .Format( "@param{0}" , j), SqlDbType.VarChar, 50)); } cmd.CommandText = sql; </p> <p> int max = 2000; for ( int i = 0; i < max; i++) { for ( int j = 0; j < 10; j++) { cmd.Parameters[j].Value = s; s = toMD5(s); } DataSet ds = new DataSet(); SqlDataAdapter da = new SqlDataAdapter(cmd); da.Fill(ds); if (++count % 10 == 0) toStatus(count, max); } DateTime end = DateTime.Now; textBox2.Text = ((TimeSpan)(end - start)).TotalSeconds.ToString( "#.0" ); } |
■LINQ to Entities
比較のために LINQ to Entities でも作ってみます。
LINQ の場合は、複数の SELECT 文をまとめることができないので、ループの中で 10 回呼び出します。この方法は、SqlDataAdapter と同じですよね。
スピードを比較したものがないので、初めて試してみたのですが、個別SELECTよりも2倍ほどおそくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | /// <summary> /// LINQ to Entities の利用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button6_Click( object sender, EventArgs e) { // 律儀にSELECT文を10回発行します。 DateTime start = DateTime.Now; string s = toMD5(start.ToString()); int count = 0; stressEntities ent = new stressEntities(); int max = 2000; for ( int i = 0; i < max; i++) { var items0 = from t in ent.table0 where t.col0 == s select t; s = toMD5(s); var item1 = from t in ent.table1 where t.col0 == s select t; s = toMD5(s); var items2 = from t in ent.table2 where t.col0 == s select t; s = toMD5(s); var items3 = from t in ent.table3 where t.col0 == s select t; s = toMD5(s); var items4 = from t in ent.table4 where t.col0 == s select t; s = toMD5(s); var items5 = from t in ent.table5 where t.col0 == s select t; s = toMD5(s); var items6 = from t in ent.table6 where t.col0 == s select t; s = toMD5(s); var items7 = from t in ent.table7 where t.col0 == s select t; s = toMD5(s); var items8 = from t in ent.table8 where t.col0 == s select t; s = toMD5(s); var items9 = from t in ent.table9 where t.col0 == s select t; s = toMD5(s); var it0 = items0.SingleOrDefault(); var it1 = items0.SingleOrDefault(); var it2 = items0.SingleOrDefault(); var it3 = items0.SingleOrDefault(); var it4 = items0.SingleOrDefault(); var it5 = items0.SingleOrDefault(); var it6 = items0.SingleOrDefault(); var it7 = items0.SingleOrDefault(); var it8 = items0.SingleOrDefault(); var it9 = items0.SingleOrDefault(); if (++count % 10 == 0) toStatus(count, max); } DateTime end = DateTime.Now; textBox2.Text = ((TimeSpan)(end - start)).TotalSeconds.ToString( "#.0" ); } |
■LINQ to SQL を利用
比較のために LINQ to SQL でも動かしてみます。
不利なのは、LINQ to Entities と同じで、10 回呼び出さないといけないところですね。
スピードに関しては、LINQ to Entities と変わりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | /// <summary> /// LINQ to SQL の利用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button7_Click( object sender, EventArgs e) { // 律儀にSELECT文を10回発行します。 DateTime start = DateTime.Now; string s = toMD5(start.ToString()); int count = 0; SqlConnection cn = new SqlConnection(CNSTR); DataClasses1DataContext cnt = new DataClasses1DataContext(cn); int max = 2000; for ( int i = 0; i < max; i++) { var items0 = from t in cnt.Dtable0 where t.col0 == s select t; s = toMD5(s); var item1 = from t in cnt.Dtable1 where t.col0 == s select t; s = toMD5(s); var items2 = from t in cnt.Dtable2 where t.col0 == s select t; s = toMD5(s); var items3 = from t in cnt.Dtable3 where t.col0 == s select t; s = toMD5(s); var items4 = from t in cnt.Dtable4 where t.col0 == s select t; s = toMD5(s); var items5 = from t in cnt.Dtable5 where t.col0 == s select t; s = toMD5(s); var items6 = from t in cnt.Dtable6 where t.col0 == s select t; s = toMD5(s); var items7 = from t in cnt.Dtable7 where t.col0 == s select t; s = toMD5(s); var items8 = from t in cnt.Dtable8 where t.col0 == s select t; s = toMD5(s); var items9 = from t in cnt.Dtable9 where t.col0 == s select t; s = toMD5(s); var it0 = items0.SingleOrDefault(); var it1 = items0.SingleOrDefault(); var it2 = items0.SingleOrDefault(); var it3 = items0.SingleOrDefault(); var it4 = items0.SingleOrDefault(); var it5 = items0.SingleOrDefault(); var it6 = items0.SingleOrDefault(); var it7 = items0.SingleOrDefault(); var it8 = items0.SingleOrDefault(); var it9 = items0.SingleOrDefault(); if (++count % 10 == 0) toStatus(count, max); } DateTime end = DateTime.Now; textBox2.Text = ((TimeSpan)(end - start)).TotalSeconds.ToString( "#.0" ); } |
そんな訳で、実行時のパフォーマンスが気にかかる場合は、SqlCommand にすると 10 倍ほど早くなるよ、という話です。逆に言えば、たいしてパフォーマンスが気にならないときは、書きやすい LINQ を使ったほうがよいでしょうってことで。