削除されたユーザーの処理-別のテーブルまたは同じテーブル?


19

シナリオは、ユーザーの数が増えており、時間が経つにつれて、ユーザーが同じテーブルで現在「削除済み」(フラグ付き)としてマークしているアカウントをキャンセルすることです。

同じメールアドレス(つまり、ログイン方法)を持つユーザーが新しいアカウントを作成したい場合、再度サインアップできますが、新しいアカウントが作成されます。(すべてのアカウントに一意のIDがあるため、メールアドレスはライブおよび削除されたものの間で複製できます)。

私が気づいたのは、システム全体で、ユーザーのテーブルを常に照会する通常の過程で、ユーザーが削除されていないことを確認していますが、私が考えているのは、それを行う必要はまったくないということです... ![明確化1:「常にクエリを実行する」ということは、「... FROM users WHERE isdeleted = "0" AND ...」のようなクエリがあることを意味します。たとえば、私たちはそのクエリで、特定の日にすべての会議のために登録されているすべてのユーザーを取得する必要があるかもしれません、我々はまた、 isdeleted =「0」のユーザーから持っている-これが私のポイントが明確にありません]?

(1) continue keeping deleted users in the 'main' users table
(2) keep deleted users in a separate table (mostly required for historical
    book-keeping)

どちらのアプローチの長所と短所は何ですか?


どのような理由でユーザーを維持しますか?
ケプラ

2
これはソフト削除と呼ばれます。参照してくださいunpermenantley(ソフト削除)データベースのレコードを削除する
Sjoerd

@keppla-彼は、「歴史的な簿記」と言っています。
ChrisF

@ChrisF:私は範囲に興味がありました:彼はユーザーだけの本を保持したいですか、それともまだいくつかのデータが添付されていますか(eGコメント、支払いなど)
ケプラ

それらが削除されたと考えるのをやめるのは(本当ではありません)、アカウントがキャンセルされたと考えるの本当です。
マイクシェリル 'キャットリコール'

回答:


13

(1)削除されたユーザーを「メイン」ユーザーテーブルに保持し続ける

  • 長所:すべての場合でより簡単なクエリ
  • 短所:ユーザー数が多い場合、時間の経過とともにパフォーマンスが低下する可能性があります

(2)削除されたユーザーを別のテーブルに保存します(主に履歴の簿記に必要です)

たとえば、トリガーを使用して、削除されたユーザーを履歴テーブルに自動的に移動できます。

  • 長所:アクティブユーザーテーブルのメンテナンスが簡単になり、パフォーマンスが安定する
  • 短所:履歴テーブルに対して異なるクエリが必要です。ただし、アプリのほとんどはそれに関心があるとは想定されていないため、このマイナスの影響はおそらく限られています

11
(IsDeleted上の)パーティションテーブルは、単一のテーブルを使用した場合のパフォーマンスの問題を取り除きます。
イアン

1
@Ianは、すべてのクエリにクエリ条件としてIsDeletedが提供されていない限り(元の質問にはないようですが)、パーティション分割によってパフォーマンスが低下することさえあります。
エイドリアンシャム

1
@Adrian、私が最も一般的なクエリは、ログイン時になると、それだけで何もユーザーがログインを許可されるだろう削除されていないと仮定した。
イアン・

1
パフォーマンスの問題になり、単一のテーブルの利点が必要な場合は、isdeletedでインデックス付きビューを使用します。
ジェフ

10

同じテーブルを使用することを強くお勧めします。主な理由は、データの整合性です。多くの場合、ユーザーに応じた関係を持つ多くのテーブルが存在します。ユーザーが削除された場合、それらのレコードを孤立させたくないでしょう。
孤立したレコードがあると、制約の実施が難しくなり、履歴情報を検索するのが難しくなります。ユーザーが古いレコードをすべて復元するようにしたい場合に、ユーザーが使用済みの電子メールを提供するかどうかを考慮する他の動作。これは、ソフト削除を使用して自動的に機能します。それをコーディングする限り、たとえば私の現在のc#linqアプリケーションでは、where deleted = 0句がすべてのクエリの最後に自動的に追加されます


7

「私が気づいたのは、システム全体で、通常の過程で、ユーザーテーブルが常に削除されていないことを確認してユーザーテーブルをクエリすることです」

これは私にデザインの悪臭を与えます。このような種類のロジックは非表示にする必要があります。たとえば、次のようなことを行う代わりに、「システム全体」で使用UserServiceするメソッドisValidUser(userId)を提供する必要があります。

「ユーザーレコードを取得し、ユーザーに削除済みフラグが設定されているかどうかを確認します」。

削除されたユーザーを保存する方法は、ビジネスロジックに影響しないようにする必要があります。

このような種類のカプセル化では、上記の引数は永続性のアプローチに影響を与えません。その後、永続性自体に関連する長所と短所にもっと集中できます。

考慮すべき事項は次のとおりです。

  • 削除されたレコードを実際にパージする期間はどれくらいですか?
  • 削除されたレコードの割合は何ですか?
  • 参照整合性(テーブルから実際にユーザーを削除する場合など)に問題が発生しますか?
  • ユーザーを再度開くことを検討していますか?

通常、私は組み合わせた方法を取ります:

  1. レコードを削除済みとしてフラグ付けします(ACを再度開く、最近閉じたACをチェックするなどの機能要件のために保持するため)。
  2. 事前定義された期間の後、削除されたレコードをアーカイブテーブルに移動します(簿記のため)。
  3. 事前に定義されたアーカイブ期間の後に削除します。

1
[明確化1:「常にクエリを実行する」ということは、「... FROM users WHERE isdeleted = "0" AND ...」のようなクエリがあることを意味します。たとえば、私たちはそのクエリで、特定の日にすべての会議のために登録されているすべてのユーザーを取得する必要があるかもしれません、我々はまた、 isdeleted =「0」のユーザーから持っている- ?これは私のポイントを明確にするん] @Adrian
アランはビート

うん、はるかに明確です。:)私がそれをしている場合、物理的/論理的削除と見なすのではなく、ユーザーのステータスの変更として作成します。コードの量は減りませんが( "and isDeleted = '0'" vs 'and "state <>' TERMINATED '")、すべてがはるかに合理的に見え、ユーザー状態も異なるのが普通です。前回の回答で示唆されたように、TERMINATEDユーザーの定期的なパージも実行できます)
Adrian Shum

5

この質問に適切に回答するには、最初に以下を決定する必要があります。このシステム/アプリケーションのコンテキストで「削除」とはどういう意味ですか?

答えるためにその質問を、あなたはまだ別の質問に答える必要があります。なぜレコードが削除されていますか?

ユーザーがデータを削除する必要がある理由はいくつかあります。通常、削除が必要になる理由は1 つだけ(テーブルごとに)あることがわかります。以下に例を示します。

  • ディスク容量を再利用するには;
  • 保持/プライバシーポリシーに従ってハード削除が必要です。
  • 破損/絶望的に不正確なデータ。修復よりも削除および再生成が容易です。
  • 行の大部分が削除されます。たとえば、ログテーブルがXレコード/日に制限されます。

ハード削除の非常に悪い理由もいくつかあります(これらの理由については後で詳しく説明します)。

  • 軽微なエラーを修正します。これは通常、開発者の怠lazと敵対的なUIを強調しています。
  • トランザクションを「無効」にする(たとえば、請求されるべきではない請求書)。
  • あなたがいるのでできます

どうしてそんなに大したことなの?良いoleの何が問題なのDELETEですか?

  • リモートでお金に結び付けられているシステムでも、ハード削除は、アーカイブ/廃棄テーブルに移動された場合でも、あらゆる種類の会計上の期待に違反します。これを処理する正しい方法は、遡及的なイベントです。
  • アーカイブテーブルは、ライブスキーマとは異なる傾向があります。新しく追加された列またはカスケードを1つでも忘れると、そのデータは永久に失われます。
  • ハード削除は、特にカスケードの場合、非常に高価な操作になる可能性があります。多くの人が気付いていないという複数のレベルをカスケード接続(あるいは場合によっては任意のカスケード、DBMSに依存する)、レコード・レベルの操作の代わりに、一連の操作になります。
  • 頻繁にハード削除を繰り返すと、インデックスの断片化のプロセスが高速化されます。

それで、ソフト削除の方が良いでしょう?いいえ、そうでもありません:

  • カスケードの設定は非常に困難になります。ほとんど常に、孤立した行としてクライアントに表示される結果になります。
  • 1つの削除のみを追跡できます。行が複数回削除および削除解除された場合はどうなりますか?
  • 読み取りパフォーマンスは低下しますが、これはパーティション化、ビュー、および/またはフィルター選択されたインデックスによって多少軽減できます。
  • 前に示唆したように、実際にはいくつかのシナリオ/管轄区域では違法である可能性があります。

真実は、これらのアプローチの両方が間違っているということです。削除は間違っています。 実際にこの質問をしているのであれば、トランザクションではなく現在の状態をモデリングしていることになります。これは、データベースランドでは悪い、悪い習慣です。

Udi Dahanはこれについて、Do n't Delete-Just Do n'tに書いてます。「削除」を実際に表す何らかの種類のタスク、トランザクション、アクティビティ、または(私の優先用語)イベント常に存在します。後でパフォーマンスのために「現在の状態」テーブルに非正規化する場合は問題ありませんが、前にではなく、トランザクションモデルを打ち込んだ後に行います。

この場合、「ユーザー」がいます。ユーザーは基本的に顧客です。顧客はあなたと取引関係があります。彼らのアカウントをキャンセルしたため、その関係は単に空に消えることはありません。実際に起こっていることは:

  • 顧客がアカウントを作成します
  • 顧客がアカウントをキャンセルする
  • 顧客がアカウントを更新する
  • 顧客がアカウントをキャンセルする
  • ...

いずれの場合も、同じ顧客であり、場合によっては同じアカウントです(つまり、各アカウントの更新は新しいサービス契約です)。では、なぜ行を削除するのですか?これは非常に簡単にモデル化できます:

+-----------+       +-------------+       +-----------------+
| Account   | --->* | Agreement   | --->* | AgreementStatus |
+-----------+       +-------------+       +----------------+
| Id        |       | Id          |       | AgreementId     |
| Name      |       | AccountId   |       | EffectiveDate   |
| Email     |       | ...         |       | StatusCode      |
+-----------+       +-------------+       +-----------------+

それでおしまい。これですべてです。何も削除する必要はありません。上記はかなりの柔軟性に対応するかなり一般的な設計ですが、少し単純化することもできます。「Agreement」レベルは不要で、「Account」を「AccountStatus」テーブルに移動させるだけでよいと判断するかもしれません。

アプリケーションで頻繁にアクティブな契約/アカウントのリストを取得する必要がある場合、それは(少し)トリッキーなクエリですが、それがビューの目的です。

CREATE VIEW ActiveAgreements AS
SELECT agg.Id, agg.AccountId, acc.Name, acc.Email, s.EffectiveDate, ...
FROM AgreementStatus s
INNER JOIN Agreement agg
    ON agg.Id = s.AgreementId
INNER JOIN Account acc
    ON acc.Id = agg.AccountId
WHERE s.StatusCode = 'ACTIVE'
AND NOT EXISTS
(
    SELECT 1
    FROM AgreementStatus so
    WHERE so.AgreementId = s.AgreementId
    AND so.EffectiveDate > s.EffectiveDate
)

これで完了です。これで、ソフト削除のすべての利点があり、欠点はないものがあります。

  • すべてのレコードが常に表示されるため、孤立レコードは問題ではありません。必要に応じて別のビューから選択するだけです。
  • 通常、「削除」は非常に安価な操作です。イベントテーブルに1行を挿入するだけです。
  • 、任意の歴史を失うことのチャンス決してありません今まで、あなたが台無しにどのようにひどくどんなに。
  • あなたはまだアカウントをハード削除することができた場合、あなたが(プライバシー上の理由など)に必要な、および削除は、アプリケーション/データベースの他の部分に干渉きれいに起こるとしないという知識を快適にします。

取り組まなければならない唯一の問題は、パフォーマンスの問題です。多くの場合、クラスター化インデックスがオンになっているため、実際には問題ではないことが判明していますAgreementStatus (AgreementId, EffectiveDate)。そこでは、I / Oシークがほとんど行われていません。ただし、それが問題になる場合は、トリガー、インデックス付き/マテリアライズドビュー、アプリケーションレベルのイベントなどを使用して、それを解決する方法があります。

ただし、パフォーマンスを早めに心配しないでください。設計を正しくすることがより重要です。この場合の「正しい」とは、トランザクションシステムとしてデータベースを使用する方法でデータベースを使用することを意味します。


1

現在、すべてのテーブルにソフト削除の削除済みフラグが設定されているシステムを使用しています。 それはすべての存在の悩みの種です。 ユーザーが1つのテーブルからレコードを「削除」できる場合、リレーショナル整合性が完全に壊れますが、そのテーブルに戻るFKの子レコードはカスケード削除されません。時間が経過すると、ゴミデータが本当に作成されます。

そのため、個別の履歴テーブルをお勧めします。


カスケードされた履歴シフトがなくても、まったく同じ問題がありますか?
グレナトロン

アクティブなレコードテーブルにはありません。
ジェシーC.スライサー

では、ユーザーが履歴テーブルに委託された後、ユーザーテーブルからFKを送信した子レコードはどうなりますか?
グレナトロン

トリガー(またはビジネスロジック)は、子レコードもそれぞれの履歴テーブルに委託します。要点は、RIを破ったことをデータベースに通知せずに、親レコードを(履歴に移動するために)物理的に削除することはできません。したがって、あなたはそれを設計することを余儀なくされます。削除されたフラグはカスケードのソフト削除を強制しません。
ジェシーC.スライサー

3
ソフト削除の実際の意味に依存します。それらを非アクティブ化する方法にすぎない場合、非アクティブ化されたアカウントに関連するレコードを調整する必要はありません。単なるデータのように思えます。そして、はい、私は設計しなかったシステムでも同様に対処しなければなりません。あなたがそれを好きにしなければならないという意味ではありません。
JeffO

1

テーブルを2つに分割することは、考えられる限りで最も遅いことです。

ここに私がお勧めする2つの非常に簡単なステップがあります:

  1. 「users」テーブルの名前を「allusers」に変更します。
  2. 「users」というビューを「select * from allusers where deleted = false」として作成します。

PS回答が数ヶ月間遅れてすみません!


0

誰かが同じ電子メールアドレスで戻ってきたときに削除されたアカウントを回復していたら、すべてのユーザーを同じテーブルに入れていたでしょう。これにより、アカウントの復旧プロセスが簡単になります。

ただし、新しいアカウントを作成する場合、削除されたアカウントを別のテーブルに移動する方がおそらく簡単です。ライブシステムはこの情報を必要としないため、公開しないでください。あなたが言うように、それはより大きなデータセットでクエリをより簡単に、そしておそらくより速くします。シンプルなコードは保守も簡単です。


0

使用中のDBMSについては言及しません。適切なライセンスを持つOracleがある場合は、ユーザーテーブルを2つのパーティション(アクティブユーザーと削除済みユーザー)に分割することを検討できます。


次に、ユーザーを削除するときに、あるパーティションから別のパーティションに行を移動する必要がありますが、これは間違いなくパーティションの使用方法ではありません。
ペテルトレック

@ピーター:え?削除済みフラグを含め、必要な条件でパーティション分割できます。
アーロンノート

@ Aaronaught、OK、私はそれを間違って言いました。DBMSはあなたのために仕事をすることができますが、それはまだ余分な仕事であり(行をある場所から別の場所、場合によっては別のファイルに物理的に移動する必要があるため)、データの物理的分布を悪化させる可能性があります
ペテルトレック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.