为什么提高ActiveRecord :: Rollback不会删除在另一个线程中创建的记录

时间:2018-01-25 19:34:27

标签: ruby-on-rails activerecord

为什么提高ActiveRecord :: Rollback不会删除在另一个线程中创建的记录

代码:

namespace :import do
  task test: :environment do
    Amchart.delete_all # just to make reproduceable runs

    ActiveRecord::Base.transaction do
      Thread.new do
        sleep 0.1
        puts 'Action in thread 1'
        Amchart.create!(js: 'a', html: 'a', title: 'a')
      end.join

      sleep 0.2
      puts 'Action in main thread'
      raise ActiveRecord::Rollback
    end

    puts "Amchart.count =  #{Amchart.count}" # prints 1, should be 0
  end
end

我做什么

$ rake import:test
D, [2018-01-25T19:39:39.746612 #1] DEBUG -- :   SQL (0.5ms)  DELETE FROM "amcharts"
D, [2018-01-25T19:39:39.747092 #1] DEBUG -- :    (0.2ms)  BEGIN
Action in thread 1
D, [2018-01-25T19:39:39.880325 #1] DEBUG -- :    (0.4ms)  BEGIN
D, [2018-01-25T19:39:39.883673 #1] DEBUG -- :   SQL (0.9ms)  INSERT INTO "amcharts" ("js", "html", "title", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["js", "a"], ["html", "a"], ["title", "a"], ["created_at", "2018-01-25 19:39:39.881157"], ["updated_at", "2018-01-25 19:39:39.881157"]]
D, [2018-01-25T19:39:39.888244 #1] DEBUG -- :    (1.9ms)  COMMIT
Action in main thread
D, [2018-01-25T19:39:40.089325 #1] DEBUG -- :    (0.4ms)  ROLLBACK
D, [2018-01-25T19:39:40.091474 #1] DEBUG -- :    (0.9ms)  SELECT COUNT(*) FROM "amcharts"
Amchart.count =  1

1 个答案:

答案 0 :(得分:1)

线程使用不同的数据库连接。您没有提及您的数据库,但在默认的PostgreSQL安装中,每个连接都会隔离事务。

# frozen_string_literal: true

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"
  gem "activerecord", "5.1.3"
  gem "pg", "< 1.0"
end

require "active_record"
require "minitest/autorun"
require "logger"

# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)

ActiveRecord::Base.establish_connection(
  adapter: 'postgresql',
  database: 'test_connections',
  username: <username>,
  password: <password>,
  host: 'localhost',
  pool: 5,
  timeout: 5000
)

ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :authors, force: :true do |t|
  end
end

class Author < ActiveRecord::Base
end

class BugTest < Minitest::Test
  def test_connections
    Author.delete_all

    puts "Main thread connection id: #{ActiveRecord::Base.connection.object_id}"

    ActiveRecord::Base.transaction do
      Author.create!

      Thread.new do
        sleep 0.1
        puts 'Action in thread 1'
        puts "Child thread connection id: #{ActiveRecord::Base.connection.object_id}"
        Author.create!
      end.join

      sleep 0.2
      puts 'Action in main thread'
      raise ActiveRecord::Rollback
    end

    assert_equal 0, Author.count
  end
end

运行此测试会给出

$ ruby test_connection.rb
Fetching gem metadata from https://rubygems.org/...
Resolving dependencies...
Using concurrent-ruby 1.0.5
Using i18n 0.9.3
Using minitest 5.11.2
Using thread_safe 0.3.6
Using tzinfo 1.2.4
Using activesupport 5.1.3
Using activemodel 5.1.3
Using arel 8.0.0
Using activerecord 5.1.3
Using bundler 1.16.1
Using pg 0.21.0
-- create_table(:authors, {:force=>:true})
D, [2018-01-25T22:38:25.564985 #45697] DEBUG -- :    (4.1ms)  DROP TABLE IF EXISTS "authors"
D, [2018-01-25T22:38:25.568347 #45697] DEBUG -- :    (2.6ms)  CREATE TABLE "authors" ("id" bigserial primary key)
   -> 0.0247s
D, [2018-01-25T22:38:25.600449 #45697] DEBUG -- :   ActiveRecord::InternalMetadata Load (0.5ms)  SELECT  "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2  [["key", "environment"], ["LIMIT", 1]]
D, [2018-01-25T22:38:25.605078 #45697] DEBUG -- :    (0.2ms)  BEGIN
D, [2018-01-25T22:38:25.606997 #45697] DEBUG -- :    (1.2ms)  COMMIT
Run options: --seed 6953

# Running:

D, [2018-01-25T22:38:25.615613 #45697] DEBUG -- :   SQL (0.5ms)  DELETE FROM "authors"
Main thread connection id: 70288221049540
D, [2018-01-25T22:38:25.615908 #45697] DEBUG -- :    (0.1ms)  BEGIN
D, [2018-01-25T22:38:25.620995 #45697] DEBUG -- :   SQL (0.4ms)  INSERT INTO "authors" DEFAULT VALUES RETURNING "id"
Action in thread 1
Child thread connection id: 70288224727480
D, [2018-01-25T22:38:25.740323 #45697] DEBUG -- :    (0.1ms)  BEGIN
D, [2018-01-25T22:38:25.743468 #45697] DEBUG -- :   SQL (2.5ms)  INSERT INTO "authors" DEFAULT VALUES RETURNING "id"
D, [2018-01-25T22:38:25.745825 #45697] DEBUG -- :    (2.1ms)  COMMIT
Action in main thread
D, [2018-01-25T22:38:25.949138 #45697] DEBUG -- :    (0.3ms)  ROLLBACK
D, [2018-01-25T22:38:25.950323 #45697] DEBUG -- :    (0.6ms)  SELECT COUNT(*) FROM "authors"
F

Finished in 0.337003s, 2.9673 runs/s, 2.9673 assertions/s.

  1) Failure:
BugTest#test_connections [test_connection.rb:64]:
Expected: 0
  Actual: 1

1 runs, 1 assertions, 1 failures, 0 errors, 0 skips

请注意,ActiveRecord::Base.connection之间的object_ids不同。

当主线程回滚时子线程INSERT和COMMIT。因此,在transaction块退出后,一个Author仍保留在数据库中。