8

私はYiiプロジェクトに取り組んでいます。Yiiモデルでsave()を実行するときに、MySQLのON DUPLICATE機能( http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html )を使用するにはどうすればよいですか?

私のMySQLは次のとおりです。

CREATE TABLE `ck_space_calendar_cache` (
  `space_id` int(11) NOT NULL,
  `day` date NOT NULL,
  `available` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `price` decimal(12,2) DEFAULT NULL,
  `offer` varchar(45) DEFAULT NULL,
  `presale_date` date DEFAULT NULL,
  `presale_price` decimal(12,2) DEFAULT NULL,
  `value_x` int(11) DEFAULT NULL,
  `value_y` int(11) DEFAULT NULL,
  PRIMARY KEY (`space_id`,`day`),
  KEY `space` (`space_id`),
  CONSTRAINT `space` FOREIGN KEY (`space_id`) REFERENCES `ck_space` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

私のPHPは次のとおりです。

$cache = new SpaceCalendarCache();
$cache->attributes = $day; //Some array with attributes              
$cache->save();

主キー(sapce_id、day)に重複がある場合、文句を言うのではなく、最新のデータで更新するだけです。

私は生のSQLでそれを行う方法を知っています、私はそれを行うためのクリーンなYiiの方法があるかどうか疑問に思っていました。

4

6 に答える 6

14

Yiiでモデルを使用していますが、非常に単純です。重複するエントリがあると思われる場所にモデルをロードしてみてください。エントリが見つかった場合はモデルがロードされ、そうでない場合はnullが返されます。モデルがnullの場合は、新しいモデルを作成するだけです。restは、新しいレコードを挿入するための通常のコードです。

//try to load model with available id i.e. unique key
$model = someModel::model()->findByPk($id);  

//now check if the model is null
if(!$model) $model = new someModel();

//Apply you new changes
$model->attributes = $attributes;

//save
$model->save();

サンプルアプリYiiブログのポストコントローラーの更新方法を参照してください。関数名のスペルが間違っている可能性があります。申し訳ありません。

于 2012-01-25T18:10:46.057 に答える
5

私はあなたが守るべきだと思う以前の答えから2つの主要なポイントを繰り返しています:

  1. txyojiが指摘しているように、MySQLのみであるため、「重複キー更新時」を使用しないでください(しようとしないでください)。

  2. select->if見つからない場合は、UdaySawantinsert->elseによって示される挿入を優先します。

ただし、ここには別のポイントがあります。並行性です。トラフィックの少ないアプリケーションの場合、問題が発生する可能性は最小限ですが(それでもゼロになることはありません)、常に注意を払っていると思います。

トランザクションの観点からは"INSERT .. ON DUPLICATE UPDATE"、アプリケーションのメモリを選択してから挿入または更新することと同じではありません。最初は単一のトランザクションであり、2番目はそうではありません。

これが悪いシナリオです:

  1. findByPk()nullを返すレコードを選択します
  2. 他のトランザクション(他のユーザーリクエストから)は、選択に失敗したIDのレコードを挿入します
  3. 次の瞬間にあなたはそれをもう一度挿入しようとします

この場合、例外(ここで行うように、一意のキーを使用している場合)または重複したエントリが発生します。重複するエントリを取得するのははるかに困難です(通常、ユーザーに重複するレコードが表示されるまで、何も奇妙に見えません)。

ここでの解決策は、「シリアル化可能」などの厳密な分離レベルを設定してから、トランザクションを開始することです。

yiiの例を次に示します。

Yii::app()->db->createCommand('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');

$trn = Yii::app()->db->beginTransaction();

try {
    // Try to load model with available id i.e. unique key
    // Since we're in serializable isolation level, even if
    // the record does not exist the RDBMS will lock this key
    // so nobody can insert it until you commit.
    // The same shold for the (most usual) case of findByAttributes()
    $model = someModel::model()->findByAttributes(array(
        'sapce_id' => $sapceId,
        'day' => $day
    ));  

    //now check if the model is null
    if (!$model) {
        $model = new someModel();
    }

    //Apply you new changes
    $model->attributes = $attributes;

    //save
    $model->save();

    // Commit changes
    $trn->commit();

} catch (Exception $e) {
    // Rollback transaction
    $trn->rollback();

    echo $e->getMessage();
}

少なくとも次のリンクで分離レベルの詳細を確認し、同時実行性と引き換えにデータの整合性においてすべての分離レベルが提供するものを確認できます。

http://technet.microsoft.com/en-us/library/ms173763.aspx

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html

于 2013-10-25T13:26:02.410 に答える
4

「OnDuplicateKeyUpdate」機能は、MySQLのSQLの方言に固有のものです。データ抽象化レイヤーに実装される可能性はほとんどありません。ZendDBとPropelには同等のものがありません。

try / catchで挿入を試行し、適切なエラーコードで挿入が失敗した場合は更新することで、動作をシミュレートできます。(重複キーエラー)。

于 2012-01-25T15:06:44.690 に答える
4

重複が存在するかどうかを確認したbeforeValidate()を上書きしました。もしそうなら、私は設定します$this->setIsNewRecord(false);

動作しているようです。それがどれほどパフォーマンスが良いかわからない。

于 2012-01-25T15:12:01.127 に答える
2

@txyojiによる問題の分析に同意しますが、別の解決策を使用します。

モデルのメソッドを拡張しsave()て既存のレコードを検索して更新するか、そうでない場合は新しい行を挿入することができます。

于 2012-01-25T15:10:55.030 に答える
1

次のようなtrycatchを使用する必要があります。

                try{
                    $model->save();
                }
                catch(CDbException $e){
                    $model->isNewRecord = false;
                    $model->save();
                }
于 2013-10-26T14:17:52.053 に答える