Я работаю над проектом Yii. Как я могу использовать функцию ON DUPLICATE для MySQL ( http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html ) при выполнении save () для модели Yii?
Мой 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.
Вы используете модели в Yii, это довольно просто. Попробуйте загрузить модель, где вы подозреваете, что у вас есть повторяющиеся записи, если вы обнаружите, что модель загружена, else null – это возврат. теперь, если ваша модель равна null, просто создайте новую модель. rest – ваш обычный код для вставки новой записи.
//try to load model with available id ie 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 blog. Возможно, я ошибаюсь в написании имен функций, извините за это.
Я повторяю два основных момента из предыдущих ответов, которые, как я думаю, вам следует придерживаться:
Не пытайтесь использовать «при повторном обновлении ключа» с момента своего MySQL-only, как указывает txyoji.
Предпочитайте
select->if
not found, а затемinsert->else
insert, продемонстрированную Uday Sawant.
Здесь есть еще один момент: параллелизм. Хотя для приложений с низким трафиком вероятность того, что вы столкнетесь с проблемой, минимальна (по-прежнему никогда не равна нулю), я думаю, что мы всегда будем осторожны в этом.
С точки зрения транзакций "INSERT .. ON DUPLICATE UPDATE"
не эквивалентно выбору в память вашего приложения, а затем вставке или обновлению. Первая – это одна транзакция, а вторая – нет.
Вот плохой сценарий:
- Вы выбираете свою запись, используя
findByPk()
которая возвращает null- Некоторые другие транзакции (из какого-либо другого пользовательского запроса) вставляют запись с идентификатором, который вы просто не смогли выбрать
- В следующее мгновение вы попытаетесь вставить его снова
В этом случае вы либо получите исключение (если вы работаете с уникальным ключом, как вы здесь), либо дублируетесь. Дублировать записи гораздо сложнее (обычно ничего не кажется странным, пока ваши пользователи не видят дубликаты записей).
Решение здесь – установить строгий уровень изоляции, например «serializable», а затем начать транзакцию.
Вот пример для yii:
Yii::app()->db->createCommand('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'); $trn = Yii::app()->db->beginTransaction(); try { // Try to load model with available id ie 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(); }
Вы можете узнать больше об уровнях изоляции по крайней мере в следующих ссылках и посмотреть, что каждый уровень изоляции может предложить в целостности данных в обмен на параллелизм
Я переделал beforeValidate (), где я проверил, существует ли дубликат. Если это так, я устанавливаю $this->setIsNewRecord(false);
Кажется, работает. Не уверен, насколько он эффективен.
Функция «On Duplicate Key Update» относится к диалоговому языку MySQL SQL. Его вряд ли можно реализовать на любом уровне абстракции данных. ZendDB и Propel не имеют эквивалента.
Вы можете имитировать поведение, пытаясь вставить в try / catch и обновить, если вставка завершилась неудачно с правильным кодом ошибки. (повторяющаяся ошибка ключа).
Я согласен с анализом проблемы @ txyoji, но я бы использовал другое решение.
Вы можете расширить метод save()
модели, чтобы искать существующую запись, обновить ее или вставить новую строку, если это не так.
вам нужно использовать try catch:
try{ $model->save(); } catch(CDbException $e){ $model->isNewRecord = false; $model->save(); }