web-dev-qa-db-ja.com

トランザクションがすでに開始されていることをどのように検出しますか?

Zend_Dbを使用して、トランザクション内にデータを挿入しています。私の関数はトランザクションを開始してから、トランザクションを開始しようとする別のメソッドを呼び出しますが、もちろん失敗します(MySQL5を使用しています)。したがって、問題は、トランザクションがすでに開始されていることをどのように検出するかです。コードのサンプルビットは次のとおりです。

       try {
                    Zend_Registry::get('database')->beginTransaction();

                    $totals = self::calculateTotals($Cart);
                    $PaymentInstrument = new PaymentInstrument;
                    $PaymentInstrument->create();
                    $PaymentInstrument->validate();
                    $PaymentInstrument->save();

                    Zend_Registry::get('database')->commit();
                    return true;

            } catch(Zend_Exception $e) {
                    Bootstrap::$Log->err($e->getMessage());
                    Zend_Registry::get('database')->rollBack();
                    return false;
            }

PaymentInstrument :: createの内部には、トランザクションがすでに開始されていることを示す例外を生成する別のbeginTransactionステートメントがあります。

27
xelurg

フレームワークには、トランザクションを開始したかどうかを知る方法がありません。実行するSQLステートメントを解析しないため、フレームワークが認識しない$db->query('START TRANSACTION')を使用することもできます。

重要なのは、トランザクションを開始したかどうかを追跡するのはアプリケーションの責任であるということです。フレームワークができることではありません。

一部のフレームワークがそれを実行しようとしていることを知っています。トランザクションを開始した回数をカウントし、一致する回数のコミットまたはロールバックを実行した場合にのみ解決するなどのコカマミーを実行します。しかし、これは完全に偽物です。コミットまたはロールバックが実際にそれを実行するかどうか、またはネストの別のレイヤーにあるかどうかを関数が認識できないためです。

(私がこの議論を数回行ったことがあると言えますか?:-)

edit:Propel はPHP "の概念をサポートするデータベースアクセスライブラリですトランザクションを開始すると、カウンターがインクリメントされるだけで、コミット/ロールバックによってカウンターがデクリメントされます。以下は、失敗するいくつかのシナリオを説明するメーリングリストスレッドからの抜粋です。


好むと好まざるとにかかわらず、トランザクションは「グローバル」であり、オブジェクト指向のカプセル化には従いません。

問題シナリオ#1

commit()を呼び出しますが、変更はコミットされていますか? 「内部トランザクション」内で実行している場合、そうではありません。外部トランザクションを管理するコードはロールバックを選択でき、私の知識や制御なしに変更が破棄されます。

例えば:

  1. モデルA:トランザクションを開始します
  2. モデルA:いくつかの変更を実行します
  3. モデルB:トランザクションを開始します(サイレントノーオペレーション)
  4. モデルB:いくつかの変更を実行します
  5. モデルB:コミット(サイレントノーオペレーション)
  6. モデルA:ロールバック(モデルAの変更とモデルBの変更の両方を破棄します)
  7. モデルB:WTF!?私の変更はどうなりましたか?

問題シナリオ#2

内部トランザクションはロールバックし、外部トランザクションによって行われた正当な変更を破棄する可能性があります。制御が外部コードに戻されると、トランザクションはまだアクティブであり、コミットできると考えられます。パッチを使用すると、commit()を呼び出すことができ、transDepthが0になっているため、何もコミットしなかった後、サイレントに_$transDepth_を-1に設定し、trueを返します。

問題シナリオ#3

アクティブなトランザクションがないときにcommit()またはrollback()を呼び出すと、_$transDepth_が-1に設定されます。次のbeginTransaction()は、レベルを0にインクリメントします。これは、トランザクションをロールバックまたはコミットできないことを意味します。その後のcommit()の呼び出しは、トランザクションを-1以上にデクリメントするだけであり、レベルを再度インクリメントするために別の余分なbeginTransaction()を実行するまで、コミットすることはできません。

基本的に、データベースに簿記を許可せずにアプリケーションロジックでトランザクションを管理しようとすることは、運命のアイデアです。 1つのアプリケーション要求で明示的なトランザクション制御を使用するために2つのモデルが必要な場合は、モデルごとに1つずつ、2つのDB接続を開く必要があります。次に、各モデルに独自のアクティブトランザクションを設定できます。このトランザクションは、互いに独立してコミットまたはロールバックできます。

http://www.nabble.com/Zend-Framework-Db-Table-ORM-td19691776.html を参照)

25
Bill Karwin

Try/catchを実行します。例外が(エラーコードや文字列のメッセージなどに基づいて)トランザクションがすでに開始されている場合は、続行します。それ以外の場合は、例外を再度スローします。

4
John II

BeginTransaction()の戻り値をZend_Registryに格納し、後で確認します。

2
Imran

Zend_Dbとアダプター(mysqliバージョンとPDOバージョンの両方)を見ると、トランザクションの状態をチェックするための優れた方法が実際には見当たりません。 ZFの問題 これに関して-幸いなことに、パッチがまもなくリリースされる予定です。

とりあえず、非公式のZFコードを実行したくない場合は、 mysqliのドキュメントSELECT @@autocommit現在トランザクション中かどうかを確認します(エラー...自動コミットモードではありません)。

2

InnoDBの場合、使用できるはずです

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();
1

この議論はかなり古いものです。一部の人が指摘しているように、アプリケーションでそれを行うことができます。 PHPには、バージョン5以降のメソッドがあります> = 5.3.3トランザクションの途中かどうかを確認します。PDP:: inTransaction()はtrueまたはfalseを返します。リンク http ://php.net/manual/en/pdo.intransaction.php

1

次のようにコードを記述することもできます。

try {
    Zend_Registry::get('database')->beginTransaction();
} 
catch (Exception $e) { }

try {
    $totals = self::calculateTotals($Cart);

    $PaymentInstrument = new PaymentInstrument;
    $PaymentInstrument->create();
    $PaymentInstrument->validate();
    $PaymentInstrument->save();

    Zend_Registry::get('database')->commit();
    return true;
} 
catch (Zend_Exception $e) {
    Bootstrap::$Log->err($e->getMessage());
    Zend_Registry::get('database')->rollBack();
    return false;
}
1
Dens

Zend profilerを使用して、開始をクエリテキストとして表示し、Zend_Db_Prfiler :: TRANSACTIONをクエリタイプとして表示し、その後、コミットまたはロールバックをクエリテキストとして使用しません。 (アプリケーションで-> query( "START TRANSACTION")およびzendプロファイラーが有効になっていないと仮定することにより)

0
siva kiran

Web向けPHPでは、ほとんどの場合、スクリプトは1回のWeb要求中に呼び出されます。その場合に本当にやりたいことは、トランザクションを開始し、スクリプトが終了する直前にそれをコミットすることです。何か問題が発生した場合は、例外をスローして、すべてをロールバックします。このような:

wrapper.php:

try {
   // start transaction
   include("your_script.php");
   // commit transaction
} catch (RollbackException $e) {
   // roll back transaction
}

シャーディングを使用すると、状況が少し複雑になり、複数の接続を開く場合があります。スクリプトの最後でトランザクションをコミットまたはロールバックする必要がある接続のリストにそれらを追加する必要があります。ただし、シャーディングの場合、トランザクションにグローバルミューテックスがない限り、コミット中に別のスクリプトがトランザクションをシャードにコミットする可能性があるため、同時トランザクションの真の分離またはアトミック性を簡単に実現できないことに注意してください。あなたのもの。ただし、MySQLの 分散トランザクション を確認することをお勧めします。

0

たぶん、PDO :: inTransaction ...を試すことができます。トランザクションが現在アクティブな場合はTRUEを返し、アクティブでない場合はFALSEを返します。私は自分自身をテストしていませんが、悪くはないようです!

0
Pepstille

私はその言葉が好きですが、開始されたトランザクションを追跡することはコカマミーであるというビル・カーウィンの評価に同意しません。

自分で作成していないモジュールによって呼び出される可能性のあるイベントハンドラー関数がある状況があります。私のイベントハンドラーは、データベースに多くのレコードを作成します。何かが正しく渡されなかった場合、欠落している場合、または何かがうまくいった場合は、必ずロールバックする必要があります。コードは他の人によって書かれているため、イベントハンドラーをトリガーする外部モジュールのコードがdbトランザクションを処理しているかどうかはわかりません。データベースにクエリを実行して、トランザクションが進行中であるかどうかを確認する方法が見つかりませんでした。

だから私は数え続けます。私はCodeIgniterを使用しています。これは、ネストされたdbトランザクションの使用を開始するように要求すると(たとえば、trans_start()メソッドを複数回呼び出すと)奇妙なことをするようです。つまり、外部関数もtrans_start()を使用している場合、ロールバックとコミットが正しく行われないため、イベントハンドラーにtrans_start()を含めることはできません。これらの機能を正しく管理する方法がまだわからない可能性は常にありますが、多くのテストを実行しました。

私のイベントハンドラーが知る必要があるのは、dbトランザクションが呼び出している別のモジュールによってすでに開始されているかどうかです。その場合、別の新しいトランザクションを開始せず、ロールバックやコミットも尊重しません。外部関数がdbトランザクションを開始した場合、ロールバック/コミットも処理することを信頼しています。

CodeIgniterのトランザクションメソッドのラッパー関数があり、これらの関数はカウンターをインクリメント/デクリメントします。

function transBegin(){
    //increment our number of levels
    $this->_transBegin += 1;
    //if we are only one level deep, we can create transaction
    if($this->_transBegin ==1) {
        $this->db->trans_begin();
    }
}

function transCommit(){
    if($this->_transBegin == 1) {
        //if we are only one level deep, we can commit transaction
        $this->db->trans_commit();
    }
    //decrement our number of levels
    $this->_transBegin -= 1;

}

function transRollback(){
    if($this->_transBegin == 1) {
        //if we are only one level deep, we can roll back transaction
        $this->db->trans_rollback();
    }
    //decrement our number of levels
    $this->_transBegin -= 1;
}

私の状況では、これが既存のデータベーストランザクションをチェックする唯一の方法です。そしてそれは機能します。 「アプリケーションがデータベーストランザクションを管理している」とは言いません。この状況では、それは本当に真実ではありません。アプリケーションの他の部分がdbトランザクションを開始したかどうかをチェックするだけなので、ネストされたdbトランザクションの作成を回避できます。

0
Zac Imboden