Yii INSERT ...重复更新

时间:2012-01-25 14:26:10

标签: php mysql yii on-duplicate-key yii-cmodel

我正在研究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方法来执行它。

6 个答案:

答案 0 :(得分: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博客中的post controllers update方法。对于函数名称的拼写我可能有问题,对不起。

答案 1 :(得分:5)

我想从以前的答案中重复两点,我认为你应该保留:

  
      
  1. 不要(尝试)使用“重复密钥更新”,因为它只支持MySQL,正如txyoji指出的那样。

  2.   
  3. 首选select->if未找到,然后{UST Sawant演示insert->else插页。

  4.   

但这里还有另外一点:并发。虽然对于流量较低的应用程序,您遇到麻烦的可能性很小(仍然不会为零),我想我们总是要小心这一点。

从事务的角度来看,"INSERT .. ON DUPLICATE UPDATE"并不等同于选择应用程序的内存然后插入或更新。第一个是单个交易,第二个不是。

这是一个糟糕的情况:

  
      
  1. 您使用findByPk()选择您的记录,返回null
  2.   
  3. 其他一些事务(来自其他一些用户请求)会插入一条记录,其中包含您未能选择的ID
  4.   
  5. 下次尝试再次插入
  6.   

在这种情况下,您将获得一个例外(如果您正在使用唯一键,就像您在这里一样)或重复条目。重复的条目更难以获取(在用户看到重复记录之前,通常没什么好看的。)

这里的解决方案是设置严格的隔离级别,例如“serializable”,然后开始一个事务。

以下是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

答案 2 :(得分:4)

“重复密钥更新”功能特定于MySQL的SQL方言。它不太可能在任何数据抽象层中实现。 ZendDB和Propel没有等价物。

您可以通过在try / catch中尝试插入来模拟行为,并在插入失败时使用正确的错误代码进行更新。 (重复键错误)。

答案 3 :(得分:4)

我在beforeValidate()之前覆盖了我检查是否存在重复的地方。如果有,我设置$this->setIsNewRecord(false);

似乎工作。不确定它是多么高效。

答案 4 :(得分:2)

我同意@ txyoji对问题的分析,但我会使用不同的解决方案。

您可以扩展模型的save()方法以查找现有记录,并更新它,或者如果不存在则插入新行。

答案 5 :(得分:1)

你必须像这样使用try catch:

                try{
                    $model->save();
                }
                catch(CDbException $e){
                    $model->isNewRecord = false;
                    $model->save();
                }