Я разрабатываю веб-приложение, которое совместно использует базу данных с рабочим приложением для рабочего стола (иначе я не могу изменить базу данных, только пытаюсь имитировать поведение).Модуль, над которым я сейчас работаю, будет хранить заметки в этой базе данных в таблице notes
.Я смог заставить его работать, я добавил заметки, и они появились в настольном приложении, затем через некоторое время я понял, что текст заметок и их описания перезаписываются.Просматривая строки в базе данных, я заметил, что modified_by
пользователь был установлен, сообщая, что при вставке был дубликат ключа, а затем обновлено.Первичный ключ для этой таблицы - автоинкремент, поэтому я очень растерялся.После некоторого копания я нашел таблицу с именем counters
со столбцом с именем notes
, счетчик которого соответствовал текущему индексу таблицы notes
.Прежде чем просто +1 счетчик на каждой вставке, я загрузил wireshark на сервер БД и записал трафик на порт БД и обнаружил следующее:
(Процедура добавления заметки из настольного приложения)
UPDATE counters SET in_use = 'Y';
SELECT notes FROM counters WHERE key_col = 1;
/* Desktop app uses current count for new index */
UPDATE counters SET notes = /* current count +1 */ WHERE key_col = 1;
UPDATE counters SET in_use = 'N';
/* ...Inserts new note here with explicit ID = current count ... */
Теперь я еще больше запутался.Зачем вообще устанавливать таблицу с автоинкрементом?Во-вторых, никогда не проверялось in_use
до того, как выбрать счетчик и добавить его ... так какой смысл in_use
?Может ли этот код привести к перезаписи, если два пользователя будут вставлены одновременно?Разве правильный способ сделать это - заблокировать таблицу counters
для каждой операции?Я мог бы попробовать это, но я не уверен, как настольное приложение будет обрабатывать обнаружение блокировки (на основе опыта - фатальная ошибка).
Помимо точного дублирования этой процедуры и надежды на лучшее, яне совсем уверен, куда идти отсюда.Одна мысль состоит в том, чтобы:
<?php
const MAX_ATTEMPTS = 3;
$curKey;
for($i = 0; $i < MAX_ATTEMPTS; $i++){
/*
SELECT in_use, notes from counters where key_col = 1;
...
*/
if( 'N' === $result['in_use'] ){
$curKey = $result['notes'];
/* INSERT count here - $curKey++ */
break;
}
/* Sleep for .25 seconds to allow for current operation to finish */
usleep(250000);
}
if( null == $curKey ){
throw \Exception('Could not insert note because counter table locked after '. MAX_ATTEMPTS .' attempts');
}
/* INSET note code here... */
Это выглядит нормально, но все же возможно может перезаписать, потому что а) время между выбором счетчика и вставкой нового счета б) Кажется, приложение для настольного компьютера не выполняет никакой проверки.
Есть какие-нибудь мысли / предложения?
РЕДАКТИРОВАТЬ : Создана хранимая процедура для проверки во время выбора и вставки.
DELIMITER $$
CREATE DEFINER=`testUser`@`%` FUNCTION `getNextNoteIndex`(appKey INTEGER) RETURNS int(11)
BEGIN
SELECT IF(`in_use` = 'N', `notes`, NULL) INTO @curIndex FROM `counters` WHERE `app_key` = appKey;
IF @curIndex IS NOT NULL
THEN
SET @newIndex = @curIndex + 1;
UPDATE `counters` SET `notes` = @newIndex WHERE `app_key` = appKey AND `in_use` = 'N' AND `notes` = @curIndex;
IF ROW_COUNT() = 1
THEN
RETURN @newIndex;
END IF;
END IF;
RETURN NULL;
END
Использование:
SELECT testDB.getNextNoteIndex(1) AS $index;