9

MVCC (Multi-Version Concurrency Control) を使用するようにデータベースを設計する場合、"IsLatest" などのブール フィールドまたは整数の "VersionId" を含むテーブルを作成し、更新は一切行わず、変更があった場合にのみ新しいレコードを挿入します。

MVCC は、詳細な履歴を必要とするアプリケーションの自動監査を提供し、更新ロックに関するデータベースへのプレッシャーも軽減します。短所は、最新バージョンを取得するために余分な句が必要なため、データ サイズが大幅に大きくなり、選択が遅くなることです。また、外部キーをより複雑にします。

( SQL Server のスナップショット分離レベルのような RDBMS でのネイティブ MVCC サポートについて話しているのではないことに注意してください)

これについては、Stack Overflow の他の投稿で説明されています。[todo - リンク]

普及しているエンティティ/ORM フレームワーク (Linq to Sql、ADO.NET EF、Hibernate など) のうち、このタイプの設計を明確にサポートできるのはどれでしょうか? これは、典型的な ActiveRecord の設計パターンに対する大きな変更であるため、データ モデルでこのルートに進むことを決定した人にとって、そこにあるツールの大部分が役立つかどうかはわかりません。MVCC をサポートするために外部キーをデータ モデル化する最良の方法さえわからないため、外部キーがどのように処理されるのかに特に関心があります。

4

6 に答える 6

3

同様にデータベースを設計しました (INSERT のみで、UPDATE も DELETE もありません)。

ほとんどすべての SELECT クエリは、各テーブルの現在の行 (最も高いリビジョン番号) のみのビューに対して行われました。

景色はこんな感じでした…

SELECT
    dbo.tblBook.BookId,
    dbo.tblBook.RevisionId,
    dbo.tblBook.Title,
    dbo.tblBook.AuthorId,
    dbo.tblBook.Price,
    dbo.tblBook.Deleted
FROM
    dbo.tblBook INNER JOIN
    (
        SELECT
            BookId,
            MAX(RevisionId) AS RevisionId
        FROM
            dbo.tblBook
        GROUP BY
            BookId
    ) AS CurrentBookRevision ON
    dbo.tblBook.BookId = CurrentBookRevision.BookId AND
    dbo.tblBook.RevisionId = CurrentBookRevision.RevisionId
WHERE
    dbo.tblBook.Deleted = 0

また、挿入 (および更新と削除) はすべてストアド プロシージャ (テーブルごとに 1 つ) によって処理されました。

ストアドプロシージャは次のように見えました…

ALTER procedure [dbo].[sp_Book_CreateUpdateDelete]
    @BookId      uniqueidentifier,
    @RevisionId  bigint,
    @Title       varchar(256),
    @AuthorId    uniqueidentifier,
    @Price       smallmoney,
    @Deleted     bit
as
    insert into tblBook
        (
            BookId,
            RevisionId,
            Title,
            AuthorId,
            Price,
            Deleted
        )
    values
        (
            @BookId,
            @RevisionId,
            @Title,
            @AuthorId,
            @Price,
            @Deleted
        )

Visual Basic コードでは、トランザクションごとにリビジョン番号が処理されていました…

Shared Sub Save(ByVal UserId As Guid, ByVal Explanation As String, ByVal Commands As Collections.Generic.Queue(Of SqlCommand))
    Dim Connection As SqlConnection = New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("Connection").ConnectionString)
    Connection.Open()
    Dim Transaction As SqlTransaction = Connection.BeginTransaction
    Try
        Dim RevisionId As Integer = Nothing
        Dim RevisionCommand As SqlCommand = New SqlCommand("sp_Revision_Create", Connection)
        RevisionCommand.CommandType = CommandType.StoredProcedure
        RevisionCommand.Parameters.AddWithValue("@RevisionId", 0)
        RevisionCommand.Parameters(0).SqlDbType = SqlDbType.BigInt
        RevisionCommand.Parameters(0).Direction = ParameterDirection.Output
        RevisionCommand.Parameters.AddWithValue("@UserId", UserId)
        RevisionCommand.Parameters.AddWithValue("@Explanation", Explanation)
        RevisionCommand.Transaction = Transaction
        LogDatabaseActivity(RevisionCommand)
        If RevisionCommand.ExecuteNonQuery() = 1 Then 'rows inserted
            RevisionId = CInt(RevisionCommand.Parameters(0).Value) 'generated key
        Else
            Throw New Exception("Zero rows affected.")
        End If
        For Each Command As SqlCommand In Commands
            Command.Connection = Connection
            Command.Transaction = Transaction
            Command.CommandType = CommandType.StoredProcedure
            Command.Parameters.AddWithValue("@RevisionId", RevisionId)
            LogDatabaseActivity(Command)
            If Command.ExecuteNonQuery() < 1 Then 'rows inserted
                Throw New Exception("Zero rows affected.")
            End If
        Next
        Transaction.Commit()
    Catch ex As Exception
        Transaction.Rollback()
        Throw New Exception("Rolled back transaction", ex)
    Finally
        Connection.Close()
    End Try
End Sub

各テーブルのオブジェクトを作成し、それぞれにコンストラクター、インスタンスのプロパティとメソッド、create-update-delete コマンド、一連のファインダー関数、および IComparable 並べ替え関数を含めました。膨大な量のコードでした。

1 対 1 の DB テーブルから VB オブジェクトへ...

Public Class Book
    Implements iComparable

#Region " Constructors "

    Private _BookId As Guid
    Private _RevisionId As Integer
    Private _Title As String
    Private _AuthorId As Guid
    Private _Price As Decimal
    Private _Deleted As Boolean

    ...

    Sub New(ByVal BookRow As DataRow)
        Try
            _BookId = New Guid(BookRow("BookId").ToString)
            _RevisionId = CInt(BookRow("RevisionId"))
            _Title = CStr(BookRow("Title"))
            _AuthorId = New Guid(BookRow("AuthorId").ToString)
            _Price = CDec(BookRow("Price"))
        Catch ex As Exception
            'TO DO: log exception
            Throw New Exception("DataRow does not contain valid Book data.", ex)
        End Try
    End Sub

#End Region

...

#Region " Create, Update & Delete "

    Function Save() As SqlCommand
        If _BookId = Guid.Empty Then
            _BookId = Guid.NewGuid()
        End If
        Dim Command As SqlCommand = New SqlCommand("sp_Book_CreateUpdateDelete")
        Command.Parameters.AddWithValue("@BookId", _BookId)
        Command.Parameters.AddWithValue("@Title", _Title)
        Command.Parameters.AddWithValue("@AuthorId", _AuthorId)
        Command.Parameters.AddWithValue("@Price", _Price)
        Command.Parameters.AddWithValue("@Deleted", _Deleted)
        Return Command
    End Function

    Shared Function Delete(ByVal BookId As Guid) As SqlCommand
        Dim Doomed As Book = FindByBookId(BookId)
        Doomed.Deleted = True
        Return Doomed.Save()
    End Function

    ...

#End Region

...

#Region " Finders "

    Shared Function FindByBookId(ByVal BookId As Guid, Optional ByVal TryDeleted As Boolean = False) As Book
        Dim Command As SqlCommand
        If TryDeleted Then
            Command = New SqlCommand("sp_Book_FindByBookIdTryDeleted")
        Else
            Command = New SqlCommand("sp_Book_FindByBookId")
        End If
        Command.Parameters.AddWithValue("@BookId", BookId)
        If Database.Find(Command).Rows.Count > 0 Then
            Return New Book(Database.Find(Command).Rows(0))
        Else
            Return Nothing
        End If
    End Function

このようなシステムでは、各行の過去のバージョンがすべて保持されますが、管理が非常に困難になる可能性があります。

長所:

  • 保持される履歴の合計
  • ストアド プロシージャの削減

短所:

  • データの整合性をデータベース以外のアプリケーションに依存する
  • 膨大な量のコードを書く
  • データベース内で外部キーを管理しない (さようなら、Linq から SQL への自動オブジェクト生成)
  • 保存されている過去のバージョン管理をすべて取得するための優れたユーザー インターフェイスはまだ思いつきません。

結論:

  • すぐに使える使いやすい ORM ソリューションがなければ、新しいプロジェクトでこのようなトラブルに遭遇することはありません。

Microsoft Entity Framework がこのようなデータベース設計をうまく処理できるかどうか、興味があります。

ジェフと残りのスタック オーバーフロー チームは、スタック オーバーフローの開発中に同様の問題に対処しなければならなかったに違いありません。編集された質問と回答の過去のリビジョンは保存され、取得可能です。

Jeff は、彼のチームが Linq to SQL と MS SQL Server を使用したと述べていると思います。

彼らはこれらの問題をどのように処理したのだろうか。

于 2008-09-08T16:38:15.557 に答える
3

ストアド プロシージャとビューを使用してデータ操作を処理し、MVCC 層を純粋に DB に実装することを検討するかもしれません。次に、ストアド プロシージャとのマッピングが可能な任意の ORM に合理的な API を提示し、DB にデータの整合性の問題を処理させることができます (そのために構築されているため)。この方法で行った場合は、IBatis や IBatis.net などのより純粋なマッピング ソリューションを検討することをお勧めします。

于 2008-09-05T20:04:33.120 に答える
1

私の知る限りでは、ORM フレームワークは CRUD コードを生成する必要があるため、MVCC オプションを実装するように明示的に設計する必要があります。箱から出してそうするものは知りません。

エンティティ フレームワークの観点からは、CSLA は永続性をまったく実装していません。必要な永続性を実装するために使用する「データ アダプター」インターフェイスを定義しているだけです。したがって、コード生成 (CodeSmith など) テンプレートを設定して、MVCC データベース アーキテクチャに沿った CSLA エンティティの CRUD ロジックを自動生成できます。

このアプローチは、おそらく CSLA だけでなく、あらゆるエンティティ フレームワークで機能しますが、CSLA での非常に「クリーンな」実装になります。

于 2008-09-05T19:53:18.560 に答える
1

Envers プロジェクトを確認してください - JPA/Hibernate アプリケーションでうまく動作し、基本的にはそれを行います - 別のテーブルで各エンティティのさまざまなバージョンを追跡し、SVN のような可能性を提供します (「2008-11 で使用されている Person のバージョンを教えてください。 -05...")

http://www.jboss.org/envers/

/イェンス

于 2008-11-06T09:37:23.787 に答える
0

私はいつも、更新と削除で db トリガーを使用して、これらの行を TableName_Audit テーブルにプッシュすることを考えていました。

それはORMで機能し、履歴を提供し、そのテーブルで選択したパフォーマンスを損なうことはありません. それは良い考えですか、それとも何か不足していますか?

于 2008-09-08T17:17:01.843 に答える
0

私たちがしていることは、通常の ORM ( hibernate ) を使用し、トリガーの代わりにビュー + で MVCC を処理することです。

したがって、通常のテーブルのように見える v_emp ビューがあります。これを行うと、トリガーは実際に正しいデータをベース テーブルに挿入します。

いいえ..私はこの方法が嫌いです:)ティムが提案したように、ストアドプロシージャAPIを使用します。

于 2008-11-06T09:55:13.920 に答える