8

我正在做一个 Yii 项目。在 Yii 模型上执行 save() 时,如何使用 MySQL ( http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html ) 的 ON DUPLICATE 功能?

我的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。现在,如果您的模型为空,只需创建新模型。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 控制器更新方法。我可能对函数名的拼写有误,对此感到抱歉。

于 2012-01-25T18:10:46.057 回答
5

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

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

  2. 更喜欢Uday Sawant 演示的select->if未找到然后插入。insert->else

不过,这里还有一点:并发。尽管对于低流量应用程序,您遇到麻烦的可能性很小(仍然永远不会为零),但我认为我们始终对此保持谨慎。

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

这是一个糟糕的场景:

  1. 您确实选择了使用findByPk()返回 null的记录
  2. 其他一些事务(来自其他一些用户请求)插入一条带有您刚刚未能选择的 id 的记录
  3. 在下一个瞬间,您尝试再次插入它

在这种情况下,您将获得异常(如果您正在使用唯一键,就像您在此处所做的那样)或重复条目。重复条目更难拾取(通常在您的用户看到重复记录之前没有什么奇怪的)。

这里的解决方案是设置一个严格的隔离级别,例如“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

于 2013-10-25T13:26:02.410 回答
4

我覆盖了 beforeValidate() ,在那里我检查了是否存在重复项。如果有,我设置$this->setIsNewRecord(false);

似乎工作。不确定它的性能如何。

于 2012-01-25T15:12:01.127 回答
4

“On Duplicate Key Update”特性特定于 MySQL 的 SQL 方言。它不太可能在任何数据抽象层中实现。ZendDB 和 Propel 没有等价物。

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

于 2012-01-25T15:06:44.690 回答
2

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

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

于 2012-01-25T15:10:55.030 回答
1

你必须像这样使用 try catch:

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