5

私は少し混乱しています。ダイレクト メール サービス内で PHP RedBean を ORM として積極的に使用していますが、興味深い状況に遭遇しました。一意のキー制約 (つまり、subscriber_id、delivery_id) を持つテーブルと、このテーブルにデータを書き込む 2 つのスクリプトがあります。テーブルを挿入または更新するソース コードがあります。

public static function addOpenPrecedent($nSubscriberId, $nDeliveryId)
{
    $oOpenStatBean = \R::findOrDispense('open_stat', 'delivery_id = :did AND subscriber_id = :sid', array(':did' => $nDeliveryId, ':sid' => $nSubscriberId));

    $oOpenStatBean = array_values($oOpenStatBean);
    if (1 !== count($oOpenStatBean)) {
        throw new ModelOpenStatException(
            "Ошибка при обновлении статистики открытий: пара (delivery_id,
            subscriber_id) не является уникальной: ($nDeliveryId, $nSubscriberId).");
    }

    $oOpenStatBean = $oOpenStatBean[0];
    if (!empty($oOpenStatBean->last_add_dt)) {
        $oOpenStatBean->precedent++;
    } else {
        $oOpenStatBean->delivery_id   = $nDeliveryId;
        $oOpenStatBean->subscriber_id = $nSubscriberId;
    }

    $oOpenStatBean->last_add_dt = time('Y-m-d H:i:s');
    \R::store($oOpenStatBean);
}

2 つのスクリプトから両方とも呼び出されます。また、競合状態が発生するため、このテーブルの一意の制約が定期的に破損するという問題があります。SQLの「重複キー更新時のINSERT」機能について知っています。しかし、ORM を純粋に使用して同じ結果を得るにはどうすればよいでしょうか?

4

1 に答える 1

3

現在、私が知っていることですが、Redbean は

INSERT ON DUPLICATE KEY UPDATE

上記のコメントで引用されたこれに関する議論は、Redbean の開発者が upsert を ORM の中間段階を汚染するビジネス ロジックであると見なしていることを示しているためです。そうは言っても、ドキュメントに従ってカスタムクエリライターまたはプラグインを使用してRedbeanを拡張する場合、これはおそらく達成可能です。以下の方法では、ORM の内部とプラグインをいじらずにこの動作を簡単に実現できるため、私はこれを試していませんが、トランザクションとモデル、およびいくつかの追加のクエリを使用する必要があります。

基本的に、R::store() を呼び出す前に、R::transaction() または R::begin() でトランザクションを開始します。次に、「FUSE」モデルで、「更新」FUSE メソッドを使用して、重複をチェックし、必要な行をロックしながら既存の ID を取得するクエリを実行します (つまり、SELECT FOR UPDATE)。ID が返されない場合は、正常であり、通常のモデル検証 (またはその欠如) を通常どおり続行して戻ります。ID が見つかった場合は、単に $this->bean->id を返された値に設定すると、Redbean は INSERT ではなく UPDATE します。したがって、次のようなモデルを使用します。

class Model_OpenStat extends RedBean_SimpleModel{
  function update(){
     $sql = 'SELECT * FROM `open_stat` WHERE `delivery_id`=? AND 'subscriber_id'=? LIMIT 1 FOR UPDATE';
     $args = array( $this->bean->deliver_id, $this->bean->subscriber_id );
     $dupRow = R::getRow( $sql, $args );
     if( is_array( $dupRow ) && isset( $dupRow['id'] ) ){
        foreach( $this->bean->getProperties() as $property => $value ){
          #set your criteria here for which fields
          #should be from the one in the database and which should come from this copy
          #this version simply takes all unset values in the current and sets them
          #from the one in the database
          if( !isset( $value ) && isset( $dupRow[$property] ) )
            $this->bean->$property = $dupRow[$property];
        }
        $this->bean->id = $dupId['id']; #set id to the duplicates id
     }
     return true;
  }
}

次に、 R::store() 呼び出しを次のように変更します。

\R::begin();
\R::store($oOpenStatBean);
\R::commit();

また

\R::transaction( function() use ( $oOpenStatBean ){ R::store( $oOpenStatBean ); } );

トランザクションにより、「FOR UPDATE」句が見つかった行をロックするか、行が見つからない場合は、新しい行が移動するインデックス内の場所をロックして、同時実行の問題が発生しないようにします。

現在、これはあるユーザーのレコードの更新が別のユーザーを上書きすることを解決するものではありませんが、それはまったく別のトピックです.

于 2014-09-01T20:55:31.677 に答える