Я повторяю два основных пункта из предыдущих ответов. Думаю, вам следует сохранить:
Не пытайтесь использовать (при попытке обновить дубликат ключа), так какТолько для MySQL, как указывает txyoji.
Предпочитают вставку select->if
не найдена, тогда insert->else
продемонстрирована Удаем Савантом.
Однако здесь есть еще один момент: параллелизм.Хотя для приложений с низким трафиком вероятность возникновения проблем минимальна (но никогда не равна нулю), я думаю, что мы всегда будем осторожны с этим.
С точки зрения транзакций, "INSERT .. ON DUPLICATE UPDATE"
не эквивалентендля выбора в памяти вашего приложения, а затем вставки или обновления.Первая - это одна транзакция, а вторая - нет.
Вот плохой сценарий:
- Вы выбираете свою запись, используя
findByPk()
, которая возвращает ноль - Какая-то другая транзакция (из другого пользовательского запроса) вставляет запись с идентификатором, который вы просто не смогли выбрать
- В следующий момент вы пытаетесь вставить ее снова
В этом случае вы получите либо исключение (если вы работаете с уникальным ключом, как здесь), либо дублирующую запись.Дублировать записи гораздо сложнее (обычно ничего не кажется странным, пока ваши пользователи не увидят дублирующиеся записи).
Решение здесь состоит в том, чтобы установить строгий уровень изоляции, например, «сериализуемый», а затем начать транзакцию.
Вот пример для 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