Yii INSERT ...重复更新

Nat*_*n H 7 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;
Run Code Online (Sandbox Code Playgroud)

我的PHP是如下:

$cache = new SpaceCalendarCache();
$cache->attributes = $day; //Some array with attributes              
$cache->save();
Run Code Online (Sandbox Code Playgroud)

如果我的主键(sapce_id,day)中有重复项,我不希望它抱怨,我只是希望它使用最新数据进行更新.

我知道如何在原始SQL中执行它,我只是想知道是否有一个干净的Yii方法来做它.

Uda*_*ant 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();
Run Code Online (Sandbox Code Playgroud)

请参阅示例应用程序Yii博客中的post controllers update方法.对于函数名称的拼写我可能有问题,对不起.

  • 当做findbypk YII在内部做一个'选择'查询时,我不认为这是问题的目的. (3认同)
  • 是的,Yii对`findByPk()`进行选择调用.问题的目的是更新一行(如果已存在)或插入新行.在使用模型时,这就是它的方式.当然它的性能损失,但如果你不想覆盖Yii,这是最好的选择.请提出任何更好的选择,这将是非常有帮助的.多谢,伙计... (2认同)

Kos*_*ios 5

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

  1. txyoji指出,不要(尝试)使用"重复密钥更新",因为它只支持MySQL.

  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();
}
Run Code Online (Sandbox Code Playgroud)

您可以在以下链接中看到有关隔离级别的更多信息,并查看每个隔离级别在数据完整性方面提供的内容以换取并发性

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

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


Nat*_*n H 4

我重写了 beforeValidate() ,检查是否存在重复项。如果有人这样做,我设置$this->setIsNewRecord(false);

似乎有效。不确定它的性能如何。

  • 但这是“之前”验证。所以这只是在检查之前运行代码的一种方法。 (2认同)