1レコードへの連続アクセスのテスト

最終更新日: 2010年 9月 26日
HOME BACK

1レコードへの連続アクセスのテスト

WEBサービスなどでは1レコードに複数プロセスがSELECTとUPDATEを連続して行うことがあります。 そのような連続アクセスの場合、トランザクション分離レベルやクエリーによって動作がどのようになるかを確認してみました。

  1. 環境
  2. テスト環境は以下のようになります。

  3. テストスクリプト
  4. 各データーベースのテストスクリプトと結果の詳細は以下にあります。

    テストスクリプトの概略は以下のようになります。 forkでプロセスを5個作成して、それぞれのプロセスが200まで連続して加算しています。 最後のプロセスのcountが1000になれば正常にカウントされたことになります。 1000に満たない場合はカウント漏れが発生していています。

    TABLE_NAME = 'test_postgres'
    PROCESS_MAX = 5
    TEST_COUNT_MAX = 200
    
    def pg_countup(max, title)
      print("\n", title, "\n")
      tp = MyPostgres.new(TABLE_NAME)
      tp.db_init()
    
      1.upto(PROCESS_MAX) do |index|
        fork do
          testdb = MyPostgres.new(TABLE_NAME, 'fork')
          testdb.query(index, max) { |num, i, conn|
             yield num, i, conn
          }
        end
      end
      ret = Process.waitall
    end
    
    def test_postgres()
      tblname = TABLE_NAME
      max = TEST_COUNT_MAX
    
      title = "UPDATE #{tblname} SET count=count+1 WHERE id=1"
      pg_countup(max, title) { |num, i, conn|
        conn.exec("UPDATE #{tblname} SET count=count+1 WHERE id=1;")
      }
      
      ・・・ 省略 ・・・
    end
    
    test_postgres()

  5. 結果
  6. PostgreSQLとMySQLでテストした結果をまとめました。

    最後のプロセスのcountが1000になれば正常にカウントされたことになり、 以下の表では○で表しています。 1000に満たない場合はカウント漏れが発生していて、表では×(最終カウント数)で表示しています。 ×の場合の最終カウント数は実行するたびに変わりますでの、この表の最終カウント数は1実行例です。

    テスト項目 PostgreSQL用のクエリー(※1) PostgreSQL MySQL
    UPDATE UPDATE test_postgres SET count=count+1 WHERE id=1;
    READ UNCOMMITTED

    SELECT FOR UPDATE
    BEGIN;
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    SELECT count AS current FROM test_postgres WHERE id=1 FOR UPDATE;
    UPDATE test_postgres SET count=1+current WHERE id=1;
    COMMIT;
    デッドロック
    READ COMMITTED

    SELECT FOR UPDATE
    BEGIN;
    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SELECT count AS current FROM test_postgres WHERE id=1 FOR UPDATE;
    UPDATE test_postgres SET count=1+current WHERE id=1;
    COMMIT;
    デッドロック
    REPEATABLE READ

    SELECT FOR UPDATE
    BEGIN;
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    SELECT count AS current FROM test_postgres WHERE id=1 FOR UPDATE;
    UPDATE test_postgres SET count=1+current WHERE id=1;
    COMMIT;
    エラー(※2)
    SERIALIZABLE

    SELECT FOR UPDATE
    BEGIN;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    SELECT count AS current FROM test_postgres WHERE id=1 FOR UPDATE;
    UPDATE test_postgres SET count=1+current WHERE id=1;
    COMMIT;
    エラー(※2)
    READ UNCOMMITTED

    SELECT
    BEGIN;
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    SELECT count AS current FROM test_postgres WHERE id=1;
    UPDATE test_postgres SET count=1+current WHERE id=1;
    COMMIT;
    ×(239) ×(618)
    READ COMMITTED

    SELECT
    BEGIN;
    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SELECT count AS current FROM test_postgres WHERE id=1;
    UPDATE test_postgres SET count=1+current WHERE id=1;
    COMMIT;
    ×(263) ×(741)
    REPEATABLE READ

    SELECT
    BEGIN;
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    SELECT count AS current FROM test_postgres WHERE id=1;
    UPDATE test_postgres SET count=1+current WHERE id=1;
    COMMIT;
    エラー(※2) ×(622)
    SERIALIZABLE

    SELECT
    BEGIN;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    SELECT count AS current FROM test_postgres WHERE id=1;
    UPDATE test_postgres SET count=1+current WHERE id=1;
    COMMIT;
    エラー(※2) デッドロック
    SELECT

    UPDATE
    UPDATE test_postgres SET count=1+(SELECT count
    FROM test_postgres WHERE id=1) WHERE id=1;
    ×(232) エラー

    ※1 クエリーはPostgreSQL用を表示しています。 今回のテストではdbiを使用していないので、MySQLとPostgreSQLで、トランザクションの開始とトランザクション分離レベルの設定の順番が異なります。

    MySQL用のクエリー例
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
    BEGIN
    SELECT count AS current FROM test_innodb WHERE id=1 FOR UPDATE;
    UPDATE test_innodb SET count=1+current WHERE id=1;
    COMMIT

    ※2 PostgreSQLのテスト結果の注意点



HOME BACK