🙄

トランザクション処理 - ACID特性やPythonでの実装例 -

2023/04/12に公開

トランザクション処理とは

トランザクション処理とは、データベースや分散システムにおいて一連の操作をまとめて実行し、データの整合性や安全性を保つための手法です。トランザクションは、一連の操作が全て成功するか、あるいは失敗した場合は操作が一つも実行されなかったかのように扱われるため、データの整合性が保たれます。この特性は、"ACID特性" と呼ばれ、トランザクション処理において重要な役割を果たします。

ACID特性

トランザクション処理が満たすべき性質として、ACID特性があります。これは以下の4つの性質からなります。

  1. Atomicity (原子性): トランザクションに含まれる操作は、すべて成功するか、一つも実行されないかのどちらかであること。
  2. Consistency (一貫性): トランザクションの実行前後でデータベースの整合性が保たれること。
  3. Isolation (独立性): 同時に実行される複数のトランザクションが、互いに影響を及ぼさないように隔離されること。
  4. Durability (永続性): トランザクションが完了した後、その結果は永続的にデータベースに保存されること。

トランザクション処理の例

以下に、銀行口座間の送金を例にトランザクション処理を説明します。

  1. 送金元の口座から送金額を引き落とす。
  2. 送金先の口座に送金額を加算する。

これら2つの操作は、どちらも成功しなければならず、一貫性が保たれる必要があります。トランザクション処理を用いることで、このような一連の操作をまとめて実行し、データの整合性が保たれます。

トランザクション処理の実践

実務では、以下のようなステップでトランザクション処理を実装します。

  1. トランザクションの開始: トランザクション処理を開始することをデータベースに通知します。
  2. 操作の実行: トランザクションに含まれる一連の操作を実行します。
  3. エラーの検出と対応: もし操作中にエラーが発生した場合、そのエラーに対応し、必要に応じてトランザクションを中断(ロールバック)または継続します。
  4. トランザクションの確定(コミット): すべての操作が正常に完了した場合、トランザクションの結果を確定(コミット)し、データベースに永続的に保存します。
  5. トランザクションの終了: トランザクション処理が終了したことをデータベースに通知します。

実践例:PythonとSQLiteを用いたトランザクション処理

以下に、PythonプログラムでSQLiteデータベースを使用したトランザクション処理の例を示します。

import sqlite3

# データベース接続を作成
conn = sqlite3.connect('example.db')

# カーソルを取得
c = conn.cursor()

try:
    # トランザクション開始
    c.execute('BEGIN')

    # 送金元の口座から送金額を引き落とす
    c.execute('UPDATE accounts SET balance = balance - ? WHERE id = ?', (amount, sender_id))

    # 送金先の口座に送金額を加算する
    c.execute('UPDATE accounts SET balance = balance + ? WHERE id = ?', (amount, recipient_id))

    # トランザクション確定(コミット)
    conn.commit()

except Exception as e:
    # エラーが発生した場合、トランザクションを中断(ロールバック)
    conn.rollback()
    print(f"An error occurred: {e}")

finally:
    # トランザクション終了
    c.close()
    conn.close()

この例では、送金元と送金先の口座の処理がトランザクションとしてまとめられており、データの整合性が保たれます。エラーが発生した場合、トランザクションはロールバックされ、操作は一つも実行されなかったかのように扱われます。

トランザクション分離レベル

トランザクション処理では、同時に実行される複数のトランザクションが互いに影響を及ぼさないようにするために、トランザクションの分離レベル(Isolation Level)という概念が存在します。トランザクション分離レベルは、同時に実行されるトランザクションがどの程度互いに影響し合うかを決定します。以下に、一般的な分離レベルとそれぞれの特性を示します。

分離レベル 説明
Read Uncommitted (未コミットの読み取り) 他のトランザクションがコミットされていなくても、変更されたデータを読み取ることができます。最も低い分離レベルで、データの整合性が損なわれる可能性があります。
Read Committed (コミット済みの読み取り) 他のトランザクションがコミットされたデータのみを読み取ることができます。データの整合性は保たれますが、同じデータに対する繰り返しの読み取りが異なる結果を返すことがあります。
Repeatable Read (繰り返し可能な読み取り) トランザクション開始時点でのデータのみを読み取ることができます。繰り返しの読み取りが同じ結果を返すことが保証されますが、ファントムリードと呼ばれる問題が発生することがあります。
Serializable (直列化可能) 同時に実行されるトランザクションが順番に実行されたかのように扱われます。最も高い分離レベルで、データの整合性が最も高いですが、パフォーマンスに影響があることがあります。

分離レベルを適切に設定することで、データの整合性とパフォーマンスのバランスを保つことができます。通常、データベースシステムはデフォルトの分離レベルを提供していますが、要件に応じて変更することができます。

分離レベルの設定例:PythonとSQLite

以下に、PythonプログラムでSQLiteデータベースのトランザクション分離レベルを設定する例を示します。

import sqlite3

# データベース接続を作成
conn = sqlite3.connect('example.db')

# 分離レベルを 'SERIALIZABLE' に設定
conn.isolation_level = 'SERIALIZABLE'

# カーソルを取得
c = conn.cursor()

try:
    # トランザクション開始
    c.execute('BEGIN')

    # ここでトランザクションに含まれる一連の操作を実行

    # トランザクション確定(コミット)
    conn.commit()

except Exception as e:
    # エラーが発生した場合、トランザクションを中断(ロールバック)
    conn.rollback()
    print(f"An error occurred: {e}")

finally:
    # トランザクション終了
    c.close()
    conn.close()

この例では、データベース接続の isolation_level プロパティに SERIALIZABLE を設定して、トランザクションの分離レベルを直列化可能にしています。他の分離レベルを設定する場合は、適切な値(READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READ など)を使用してください。

まとめ

トランザクション処理は、データベースや分散システムにおいて一連の操作をまとめて実行し、データの整合性や安全性を保つための手法です。ACID特性を満たすことで、トランザクション処理がデータの整合性を保ちます。また、トランザクション分離レベルを適切に設定することで、データの整合性とパフォーマンスのバランスを保つことができます。実務でのトランザクション処理は、トランザクションの開始から終了までのステップに従って実装されます。

Discussion