【问题标题】:Shared transaction across multiple connections, or ReadUncommitted in PostgreSQL跨多个连接的共享事务,或 PostgreSQL 中的 ReadUncommitted
【发布时间】:2014-08-14 22:38:12
【问题描述】:

我想在单个事务范围内打开多个连接,以便每个连接都可以看到前一个连接所做的更改。

我需要这个来进行测试 - 真正的代码写入数据库,测试代码验证数据是否实际插入/更新。最后我回滚事务范围,使真实数据库不受影响。

这种方法在 SQL Server 中运行良好,但在 PostgreSQL 中似乎不起作用(我使用 9.3 和 Npgsql 提供程序),下面是一个小例子。


这是在事务范围内运行任意查询的助手

private void RunQuery(string query, Action<IDataReader> process)
{
    using (var connection = new NpgsqlConnection(Config.ConnectionString)) {
        connection.Open();
        connection.EnlistTransaction(Transaction.Current);

        using (var command = connection.CreateCommand()) {
            command.CommandText = query;
            using (var reader = command.ExecuteReader()) {
                while (reader.Read()) {
                    process(reader);
                }
            }
        }
    }
}

..这是测试代码 - 它插入到users 表中,然后检查用户是否实际插入:

using (var scope = new TransactionScope()) {

    //"tested scenario"
    int id = 0;
    RunQuery("INSERT INTO users (name) VALUES ('foo') RETURNING id;", reader => {
        id = (int)reader.GetValue(0);
    });

    //checking
    int id2 = 0;
    RunQuery("SELECT id, name FROM users WHERE id=" + id, reader => {
        id2 = (int)reader.GetValue(0);
    });    
    Assert.That(id2, Is.Not.EqualTo(0));
}

上述测试在 Postgres 上失败,因为 id2 始终为零。我用TransactionOptions.ReadUncommitted 尝试了TransactionScope 构造函数,但它似乎没有帮助。请注意,如果我对 SQL Server 运行此程序(将 NpgsqlConnection 更改为 SqlConection,使用 SCOPE_IDENTITY 检索 id),那么一切正常,id2 不为零。

正如您所料,在 Postgres 的同一连接中进行选择,但我不需要,我的目标是在共享事务范围内使用多个连接。我也不需要多线程,这些连接是按顺序发生的。

【问题讨论】:

    标签: c# sql-server postgresql transactions


    【解决方案1】:

    首先声明:虽然我对 postgresql 了解一些,但对 .NET 了解甚少。

    我怀疑您将两个相关但独立的概念混为一谈 - 分布式事务的概念和存在的事务隔离级别。

    根据.NET Documentation,EnlistTransaction 将连接添加到分布式事务中。一个分布式事务是described as follows

    分布式事务是影响多个事务的事务 资源。对于要提交的分布式事务,所有参与者 必须保证对数据的任何更改都是永久性的。改变必须 尽管系统崩溃或其他不可预见的事件仍然存在。如果即使是 单一参与者未能作出此保证,整个 事务失败,并且对数据范围内的任何更改 事务被回滚。

    在数据库中,此类事务由两阶段提交过程实现,其中实际上是数据库中的独立事务。通过执行PREPARE TRANSACTION,所有参与的交易都进入第一阶段的末尾。一旦它们都处于这种状态,就可以通过执行COMMIT PREPARED 来完全提交它们。如果它们中的任何一个在PREPARE TRANSACTION 期间失败,那么它们都可以通过ROLLBACK PREPARED 回滚。这保证了它们要么全部提交,要么全部回滚。

    当使用 .NET 提供的中间件时,您看不到任何这些细节:框架会为您处理两阶段提交。

    因此,您可能想知道这与您没有看到在此分布式事务的一部分中所做的更改反映在另一部分中这一事实有什么关系。答案可能是什么都没有。这两个事务实际上是完全独立的——事实上它们有可能位于完全独立的数据库中。

    您想要实现的目标 - 能够在提交之前查看一个事务中对另一个事务所做的更改 - 与 transaction isolation 的级别有关。

    对您来说,坏消息是听起来您想要的隔离级别是“读取未提交”,而 postgresql 不支持。

    也许您需要在更高的层次上描述您想要实现的目标 - 很可能还有另一种(也许更好)的方式来实现它。

    【讨论】:

    • "..In PostgreSQL READ UNCOMMITTED 被视为 READ COMMITTED.." - 现在我明白为什么 TransactionScope 选项没有帮助!至于我为什么需要这个 - 我更新了这个问题,想法是将自动化测试包装在事务范围内并最终回滚,以便数据库不受测试的影响。
    • @andreister 顺便说一句,您可能会发现使用数据库迁移工具(例如 Flyway 或 Liquibase)在每次运行时重新创建数据库(参见 Flyway 中的 clean)会更容易进行测试你的测试。在您的测试周围添加事务包装可能会污染您的结果。
    • @BasilBourque 测试应该足够健壮,不会在出现意外数据时中断——但如果事务回滚正确完成,我们一开始就不应该有任何意外数据。尽管如此,为每次测试运行创建一个新数据库会非常慢,所以听起来不可行 - 除非我误读了您的评论?
    猜你喜欢
    • 1970-01-01
    • 2013-01-01
    • 2014-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-03
    • 2011-11-28
    相关资源
    最近更新 更多