複雑な基準を使用したインデックス付き読み取りの最小化


12

作業チケットのFirebird 2.5データベースを最適化しています。それらはそのように宣言されたテーブルに保存されます:

CREATE TABLE TICKETS (
  TICKET_ID id PRIMARY KEY,
  JOB_ID id,
  ACTION_ID id,
  STATUS str256 DEFAULT 'Pending'
);

通常、処理されておらずPendingステータスにある最初のチケットを見つけたいです。

私の処理ループは次のようになります:

  1. 最初のチケットを取得する場所 Pending
  2. チケットを使用してください。
  3. チケットステータスの更新=> Complete
  4. 繰り返す。

派手なものは何もありません。このループの実行中にデータベースを監視している場合、各反復でインデックス付き読み取りの数が増えていることがわかります。パフォーマンスは、私が知ることができるほどひどく低下するようには見えませんが、私がテストしているマシンはかなり速いです。ただし、一部のユーザーから時間の経過とともにパフォーマンスが低下するという報告を受けました。

にインデックスがありますStatusが、それでもTicket_Id繰り返しごとに列をスキャンするようです。私は何かを見落としているように見えますが、何がわからないのですか。このようなものに対するインデックス付き読み取りの増加数は予想されていますか、それともインデックスが何らかの形で誤動作していますか?

-コメントの編集-

Firebirdでは、次のように行の取得を制限します。

Select First 1
  Job_ID, Ticket_Id
From
  Tickets
Where
  Status = 'Pending'

だから、「最初」と言うとき、私はそれをどこに限定レコードセットを要求しているだけですStatus = 'Pending'


あなたが何を意味する「最初」「第一のチケットを取得する場所を『保留』」
ypercubeᵀᴹ

最小の「最初の」を意味した場合ticket_id、あなたはprobbalyにインデックス必要がある(status, ticket_id)
ypercubeᵀᴹ

また、パフォーマンスの低下は、他のクエリ/ステートメントではなく、この手順によって引き起こされると確信していますか?
ypercubeᵀᴹ

@ypercube-いいえ、パフォーマンスの低下がどこにあるのかわかりません。それが私の質問が「これに気をつける必要があるのか​​、それともインデックスの通常の振る舞いなのか」という理由です。データベースの監視中に気づいたことであり、予期しないことだと考えました。インデックス付きの列に対してwhere句を指定した場合、前の行をスキャンし続けるとは思わないでしょう。FWIW、ticket_id実際に含めるようにインデックスを変更すると、ステータスをインデックスに登録するよりもパフォーマンスが低下します。
gddc

であるid(データ型)を使用すると、定義されたドメイン?
a_horse_with_no_name

回答:


1

「完了」ステータスにあるアイテムの数が増えるため、時間の経過とともに劣化が発生します。これについて少し考えてみてください。ステータスが「完了」の行がおそらく少数あるため、テスト時にパフォーマンスが低下することはありません。しかし、実稼働環境では、数百万行の「完了」ステータスがあり、この数は時間の経過とともに増加します。これにより、基本的に、ステータスに関するインデックスの有用性が徐々に低下します。そのため、データベースはおそらく、Statusの値がほぼ常に「Complete」であるため、インデックスを使用する代わりにテーブルをスキャンするだけであると判断します。

SQL Server(および他のRDBMSの場合もありますか?)では、フィルター処理されたインデックスを使用してこれを回避できます。SQL Serverでは、WHERE条件をインデックス定義の最後に追加して、「このインデックスをStatus <> 'Complete'のレコードにのみ適用する」と言います。次に、この述語を使用するクエリは、「Complete」に設定されていない少量のレコードのインデックスを使用する可能性が高くなります。ただし、http//www.firebirdsql.org/refdocs/langrefupd25-ddl-index.htmlのドキュメントに基づくと、Firebirdがフィルター選択されたインデックスをサポートしているようには見えません。

回避策は、ArchiveTicketsテーブルに「Complete」レコードを配置することです。Ticketsテーブルとまったく同じ定義(ただし、自動生成されたIDなし)でテーブルを作成し、ArchiveTicketsテーブルに「Complete」レコードをプッシュすることで、それらの間の行を維持します。チケットテーブルのインデックスは、レコードの数がはるかに少なくなり、パフォーマンスが大幅に向上します。これは、「完了」チケットを参照するレポートなどを変更して、アーカイブテーブルを指すようにするか、チケットとArchiveTicketの両方でUNIONを実行する必要があることを意味します。これには、高速であるという利点があるだけでなく、ArchiveTicketsテーブルに特定のインデックスを作成して、他のクエリのパフォーマンスを向上させることもできます(たとえば:

プロダクションが数千行になる場合は、これに注意する必要があります。パフォーマンスは時間とともに低下し、ユーザーエクスペリエンスに悪影響を及ぼします。


0

パフォーマンスが影響を受けるかどうかは、データ量とマシン容量の関数になります。最新のハードウェアの容量を考えると、説明する設計では処理できないチケット販売量を想像するのは困難です。ただし、正確さのために推奨する変更があり、副次的な利点としてパフォーマンスが向上する可能性があります。

あなたの最初の保留中の取得のクエリは、非決定論的です。最初はどの順番ですか?SQLテーブルには固有の順序はありません。First 1ハックは、あなたに与えているいくつかの任意の最初のものを。確定的にするために、保留中のジョブをJob_IDの順序で処理してみませんか?

2つのインデックス{Job_ID}および{Status、Job_ID}がある場合、このクエリは1行を予測可能かつ効率的に返します。

Select Job_ID, Ticket_Id
From   Tickets
Where Job_ID = ( 
  select min(Job_ID) from Tickets 
  where Status = 'Pending'
);

私はFirebirdユーザーではないため、クエリプランを確認する必要がありますが、サブクエリは2番目のインデックスのみを参照し、最初のインデックスの値を生成するため、効率的です。(他の効率的なトリックを利用できる場合があります。たとえば、物理テーブルをB +ツリーとして整理したり、非表示のrow_idにアクセスしたりできます。)

正確さのために行う他の変更はStatus、単一の制約付きバイトを作成し、アプリケーションに「保留」文字列を提供させることです。これStatusにより、誤った値から保護され、おそらくインデックスの値が小さくなります。何かのようなもの:

CREATE TABLE TICKETS (
  TICKET_ID id PRIMARY KEY,
  JOB_ID id,
  ACTION_ID id,
  STATUS char(1) not NULL 
     DEFAULT 'P'
     CHECK( STATUS in ('P', 'C', 'X') ) -- whatever the domain is
);

もちろん、ビュー(または派生列)を使用して、ステータスの正規の文字列を提供できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.