2007年07月09日
テーブル変数は遅い?
ASP.NET + SQL Server2005で作成したアプリケーションで特定のストアドプロシージャを呼ぶとかなりの頻度でタイムアウトが発生していました。
ところが、SQL Server Management Studioで実行すると、数秒で戻ってきます。
Web.configのConnectionStringや TransactionScopeのタイムアウトを設定してみても改善しませんでした。
そこで問題のあるストアドプロシージャを調べてみると、どれもテーブル変数に集計結果を保存していることがわかりました。
ちなみに落ちているときのプロファイラの結果がこれ
タイムアウトに達しました。操作が完了する前にタイムアウト期間が過ぎたか、またはサーバーが応答していません。
ところが、SQL Server Management Studioで実行すると、数秒で戻ってきます。
Web.configのConnectionStringや TransactionScopeのタイムアウトを設定してみても改善しませんでした。
そこで問題のあるストアドプロシージャを調べてみると、どれもテーブル変数に集計結果を保存していることがわかりました。
ちなみに落ちているときのプロファイラの結果がこれ
CPU
Reads
Writes
Duration
(1)
29,734
225,167
11
30,005
(2)
29,610
223,208
7
30,005
どうも Durationが30,000を超えると落ちているような気がします。
(どこかに設定するところがあるのでしょうか...)
ちなみにテーブル変数の主キーの有無でも変化はありませんでした。
インデックスを張ることもできないので、SQLを全面的に見直した方がよさそうですが、
(削除) 面倒くさい (削除ここまで)新たなバグを呼び込みそうなのでパス
ところが試しに集計結果をテーブル変数に格納するのをやめて、使用する毎にサブクエリーで結合するように改めてみたらタイムアウトが出なくなりました。
プロファイラの結果でも明らかにReads, Durationが激減しています。
本当はテーブル変数じゃなくて一時テーブルにインデックス張ってみたらどうかな...と思ってたのですが、この段階で動くようになったので今日はここまで。
(削除) 面倒くさい (削除ここまで)余計なバグを作ったら大変ですしね。
(どこかに設定するところがあるのでしょうか...)
ちなみにテーブル変数の主キーの有無でも変化はありませんでした。
インデックスを張ることもできないので、SQLを全面的に見直した方がよさそうですが、
ところが試しに集計結果をテーブル変数に格納するのをやめて、使用する毎にサブクエリーで結合するように改めてみたらタイムアウトが出なくなりました。
プロファイラの結果でも明らかにReads, Durationが激減しています。
CPU
Reads
Writes
Duration
(3)
3,954
21,1557
10
1,020
本当はテーブル変数じゃなくて一時テーブルにインデックス張ってみたらどうかな...と思ってたのですが、この段階で動くようになったので今日はここまで。
2007年06月25日
String.IsNullOrEmpty()
.NETで文字列が空白かどうかを調べる場合、String.Emptyを使います。
単に""を使う場合と比べてオブジェクトを生成しない分早い気がする。
コードの見た目がよくなる気がするといった効果があります。
さて、文字列が空白以外にNullの場合もあり得る場合、次のように書いていました。
.NET 2.0になってから ??演算子というものが追加されたようです。
SQL ServerのISNULLのような感じで値がNullであった場合に指定した値を返すことができます。
便利なのですが、あまり見た目がよくないなぁ...と思っていたところ、 同じく.NET 2.0から追加された String.IsNullOrEmpty というものを発見しました。
名前のままですが、文字列が空白かNullであるかを判定してくれます。
あまり比較している感が無いのが欠点ですが、??演算子よりは見た目がスッキリしてよいのではないでしょうか?
if (str == String.Empty) {
}
単に""を使う場合と比べてオブジェクトを生成しない分早い気がする。
コードの見た目がよくなる気がするといった効果があります。
if (str == "") {
}
さて、文字列が空白以外にNullの場合もあり得る場合、次のように書いていました。
if (str == null || str == String.Empty) {
}
.NET 2.0になってから ??演算子というものが追加されたようです。
SQL ServerのISNULLのような感じで値がNullであった場合に指定した値を返すことができます。
if ((str ?? String.Empty) == String.Empty) {
}
便利なのですが、あまり見た目がよくないなぁ...と思っていたところ、 同じく.NET 2.0から追加された String.IsNullOrEmpty というものを発見しました。
名前のままですが、文字列が空白かNullであるかを判定してくれます。
if (String.IsNullOrEmpty(str)) {
}
2007年06月22日
PrintDocumentで改ページ
.NETの System.Drawing.Printing.PrintDocumentを使って帳票印刷するときの話。
まず System.Drawing.Printing.PrintDocumentを継承して自前のドキュメントを作成します。
次に PrintPageイベントを処理するイベントハンドラを作成し、イベントハンドラの PrintPageEventArgs.Graphics を使用して実際の描画を行います。
あとはこれを System.Windows.Forms.PrintDialogあたりに食わせればちゃんと印刷してくれます。
前置きが長くなりましたね。
今回はこの MyPrintDocumentに DataTableを持たせてレコードごとに印刷をしようとして悩みました。
PrintDocument.PrintPage のヘルプによると、改ページをするためにはPrintPageイベントハンドラのPrintPageEventArgs.HasMorePagesに trueを指定するらしいので、
うまく動きません(笑 (1ページ目しか出ないよ...)
PrintPageEventArgs.HasMorePagesを trueにすることで改ページ記号のようなものを出力してくれるのだと思っていたのですが、どうやら違ったようです。
いろいろ調べた結果、PrintPageEventArgs.HasMorePagesに trueをセットして一度 PrintDocument.PrintPageを抜けてやるのが正解だったみたいです。
分かってしまえば何ともない事ですが、ヘルプの解説だけだとすぐに理解できませんでした。
まず System.Drawing.Printing.PrintDocumentを継承して自前のドキュメントを作成します。
次に PrintPageイベントを処理するイベントハンドラを作成し、イベントハンドラの PrintPageEventArgs.Graphics を使用して実際の描画を行います。
public class MyPrintDocument : System.Drawing.Printing.PrintDocument
{
// コンストラクタ
public MyPrintDocument() {
this.PrintPage +=
new System.Drawing.Printing.PrintPageEventHandler(this.MyPrintDocument_PrintPage);
}
// 印刷イベント
private void MyPrintDocument_PrintPage(object sender, PrintPageEventArgs e) {
this.DrawPage(e.Graphics)
}
// 実際の印刷処理
private void DrawPage(System.Drawing.Graphics g) {
// Graphicsオブジェクト gに対して描画を行う...
g.DrawString("hello", new Font("MS ゴシック", 9.0f), Brushes.Black, 10.0f, 10.0f);
}
}
あとはこれを System.Windows.Forms.PrintDialogあたりに食わせればちゃんと印刷してくれます。
public class MyForm : System.Windows.Forms
{
private MyPrintDocument doc;
// コンストラクタ
public MyForm() {
InitializeComponent();
this.doc = new MyForm();
}
:
// 印刷
private void PrintDocument() {
using (PrintDialog pd = new PrintDialog()) {
pd.Document = this.doc;
if (pd.ShowDialog() == DialogResult.OK) {
this.doc.Print();
}
}
}
}
前置きが長くなりましたね。
今回はこの MyPrintDocumentに DataTableを持たせてレコードごとに印刷をしようとして悩みました。
PrintDocument.PrintPage のヘルプによると、改ページをするためにはPrintPageイベントハンドラのPrintPageEventArgs.HasMorePagesに trueを指定するらしいので、
public class MyPrintDocument : System.Drawing.Printing.PrintDocument
{
private DataTable myTable;
public DataTable Table {
get { return this.myTable; }
set { this.myTable = value; }
}
// コンストラクタ
public MyPrintDocument() {
this.PrintPage +=
new System.Drawing.Printing.PrintPageEventHandler(this.MyPrintDocument_PrintPage);
}
// 印刷イベント
private void MyPrintDocument_PrintPage(object sender, PrintPageEventArgs e) {
for (int i = 0; i < myTable.Rows.Count; i++) { DrawPage(e.Graphics, myTable[i]); e.HasMorePages = true; } e.HasMorePages = false; } // 実際の印刷処理
private void DrawPage(System.Drawing.Graphics g, DataRow myRow) {
g.DrawString(myRow["Comment"], new Font("MS ゴシック", 9.0f), Brushes.Black, 10.0f, 10.0f);
}
}
うまく動きません(笑 (1ページ目しか出ないよ...)
PrintPageEventArgs.HasMorePagesを trueにすることで改ページ記号のようなものを出力してくれるのだと思っていたのですが、どうやら違ったようです。
いろいろ調べた結果、PrintPageEventArgs.HasMorePagesに trueをセットして一度 PrintDocument.PrintPageを抜けてやるのが正解だったみたいです。
public class MyPrintDocument : System.Drawing.Printing.PrintDocument
{
private DataTable myTable;
private int pageIndex;
public DataTable Table {
get { return this.myTable; }
set { this.myTable = value; }
}
// コンストラクタ
public MyPrintDocument() {
this.pageIndex = 0;
this.PrintPage +=
new System.Drawing.Printing.PrintPageEventHandler(this.MyPrintDocument_PrintPage);
}
// 印刷イベント
private void MyPrintDocument_PrintPage(object sender, PrintPageEventArgs e) {
DrawPage(e.Graphics, myTable[this.pageIndex])
if (this.currentPage < this.myTable.Rows.Count - 1) { this.pageIndex ++; e.HasMorePages = true; } else { e.HasMorePages = false; } } // 実際の印刷処理
private void DrawPage(System.Drawing.Graphics g, DataRow myRow) {
g.DrawString(myRow["Comment"], new Font("MS ゴシック", 9.0f), Brushes.Black, 10.0f, 10.0f);
}
}
分かってしまえば何ともない事ですが、ヘルプの解説だけだとすぐに理解できませんでした。
2007年06月19日
Nullable型
今日は .NETのお話。
ADO.NET 2.0になって、TableAdapterというクラスが出てきました。
以前のDataAdapterとConnectionクラスが組み合わされたようなクラスで、VisualStudioでSQLやストアドプロシージャを指定してやるとデータアクセス用のメソッドを作ってくれます。
慣れすぎると自分がサルに退化しそうなくらい便利なクラスです。
更にVisualStudioが生成するメソッドは省略可能なパラメータをNull指定可能になっています。
例えば以下のようなSQLを用意しておけば Param1~Param3のパラメータはNull指定可能になるので、Param3を指定しない場合には nullを指定して省略することができます。
さて、こんな便利なTableAdapterですが、今回プログラムの都合でパラメータをHashTableで受け取るようになりました。
HashTableに含まれていればパラメータを使用し、含まれてなければnullとしたい訳です。
何も考えずにコーディング
で、エラー。
「intとnullは違うだろ」と。
そりゃそうですね。(でも、お前もエラーメッセージの’の数間違えているけどな...)
ならば objectにキャストしてやろう。
やっぱりエラー。
今度はTableAdapter側のメソッドの型と合わない。
「objectとint?は違うだろ」と。ごもっとも。
...ん? "int?"? その"?"はナニ?
試してみるとあっさり成功。
最初にNullが指定できる時点で気付けよって感じですが、intじゃなくて int?、正しくは.Net 2.0で追加された Nullable<int>でした。
できればConvertのメソッドで直接Nullableに変換できるメソッドがあれば便利なんですけどね。
ADO.NET 2.0になって、TableAdapterというクラスが出てきました。
以前のDataAdapterとConnectionクラスが組み合わされたようなクラスで、VisualStudioでSQLやストアドプロシージャを指定してやるとデータアクセス用のメソッドを作ってくれます。
慣れすぎると自分がサルに退化しそうなくらい便利なクラスです。
更にVisualStudioが生成するメソッドは省略可能なパラメータをNull指定可能になっています。
例えば以下のようなSQLを用意しておけば Param1~Param3のパラメータはNull指定可能になるので、Param3を指定しない場合には nullを指定して省略することができます。
SELECT foo.* FROM foo WHERE ((@Param1 IS NULL) OR (foo.Param1 = @Param1)) AND ((@Param2 IS NULL) OR (foo.Param2 = @Param2)) AND ((@Param3 IS NULL) OR (foo.Param3 = @Param3))
using (FooTableAdapter ta = new FooTableAdapter()) {
ta.Fill(this.Foo, param1, param2, null);
}
さて、こんな便利なTableAdapterですが、今回プログラムの都合でパラメータをHashTableで受け取るようになりました。
HashTableに含まれていればパラメータを使用し、含まれてなければnullとしたい訳です。
何も考えずにコーディング
using (FooTableAdapter ta = new FooTableAdapter()) {
ta.Fill(this.Foo,
ht.Contains("Param1") ? Convert.ToInt32(ht["Param1"]) : null,
ht.Contains("Param2") ? Convert.ToInt32(ht["Param2"]) : null,
ht.Contains("Param3") ? Convert.ToInt32(ht["Param3"]) : null
);
}
で、エラー。
'int' と '<null>'' の間に暗黙的な変換がないため、条件式の型がわかりません。
そりゃそうですね。(でも、お前もエラーメッセージの’の数間違えているけどな...)
ならば objectにキャストしてやろう。
using (FooTableAdapter ta = new FooTableAdapter()) {
ta.Fill(this.Foo,
ht.Contains("Param1") ? Convert.ToInt32(ht["Param1"] as object : null,
ht.Contains("Param2") ? Convert.ToInt32(ht["Param2"] as object : null,
ht.Contains("Param3") ? Convert.ToInt32(ht["Param3"] as object : null
);
}
やっぱりエラー。
'FooTableAdapter.Fill(int?, int?, int?)' に最も適しているオーバーロード メソッドには無効な引数がいくつか含まれています。 引数 '1': 'object' から 'int?' に変換できません。 :
今度はTableAdapter側のメソッドの型と合わない。
「objectとint?は違うだろ」と。ごもっとも。
...ん? "int?"? その"?"はナニ?
using (FooTableAdapter ta = new FooTableAdapter()) {
ta.Fill(this.Foo,
ht.Contains("Param1") ? Convert.ToInt32(ht["Param1"] as int? : null,
ht.Contains("Param2") ? Convert.ToInt32(ht["Param2"] as int? : null,
ht.Contains("Param3") ? Convert.ToInt32(ht["Param3"] as int? : null
);
}
最初にNullが指定できる時点で気付けよって感じですが、intじゃなくて int?、正しくは.Net 2.0で追加された Nullable<int>でした。
using (FooTableAdapter ta = new FooTableAdapter()) {
ta.Fill(this.Foo,
ht.Contains("Param1") ? Convert.ToInt32(ht["Param1"] as Nullable<int> : null,
ht.Contains("Param2") ? Convert.ToInt32(ht["Param2"] as Nullable<int> : null,
ht.Contains("Param3") ? Convert.ToInt32(ht["Param3"] as Nullable<int> : null
);
}
できればConvertのメソッドで直接Nullableに変換できるメソッドがあれば便利なんですけどね。
登録:
コメント (Atom)