(PHP, MySQL) Как решить проблему одновременного доступа к данным?

+
Дата: 12.09.2006 17:46:42
На сервере есть табличка, из которой берутся данные.
Задача в том, чтобы на каждый запрос, в случае "одновременного доступа" пользователей, всегда выдавались уникальные данные.

Другими словами, если юзер А получил с 1 по 10 запись из таблицы table, то юзер Б должен получить следующие 10 записей и никак иначе.

По идее сейчас реализовано, что как только юзер А получит свою порцию данных, то панель управления "запомнит" с какой записи дальше следует выполнять запрос.

Смущает если будет очень много одновременных подключений и сервер "не успеет" обновить состояние.

Ни разу не сталкивался с подобным, поэтому такой и вопрос.
DocAl
Дата: 12.09.2006 18:32:58
В случае использования InnoDB можно применить следующий приём:
START TRANSACTION;
SELECT ... WHERE used < SUBTIME(NOW(), '00:20:00') AND ... FOR UPDATE; -- в предположении о таймауте бездействия 20 минут.
UPDATE ... SET used=NOW() ....;
COMMIT;
операции с данными на клиенте и UPDATE
Схему можно было бы упростить, если бы MySQL понимал условие "пропускать залоченные записи", что он, увы, не умеет. (Oracle, вроде бы, да)
pamir
Дата: 12.09.2006 19:14:51
+
На сервере есть табличка, из которой берутся данные.
Задача в том, чтобы на каждый запрос, в случае "одновременного доступа" пользователей, всегда выдавались уникальные данные.

А на всех хватит данных?
Странная задача, можно более четко? Возможно и решение иное...

DocAl
В случае использования InnoDB можно применить следующий приём:
START TRANSACTION;
SELECT ... WHERE used < SUBTIME(NOW(), '00:20:00') AND ... FOR UPDATE; -- в предположении о таймауте бездействия 20 минут.
UPDATE ... SET used=NOW() ....;
COMMIT;
операции с данными на клиенте и UPDATE
Схему можно было бы упростить, если бы MySQL понимал условие "пропускать залоченные записи", что он, увы, не умеет. (Oracle, вроде бы, да)

Если в mySQL конструкция for update работает также как в Оракле, то смотрим, что получится
Первый пользователь залочил строки. (update и commit еще не прошли)
В это время второй пользователь попытался их залочить..... Что тут произойдет? В оракле он будет ждать освобождения, т.к. не указана конструкция nowait. А здесь?
Если тоже ждать, то после того, как транзакция у первого пользователя завершится, второй, дождавшись, успешно залочит и поправит их под себя. Я не прав?

PS. Оракл не умеет пропускать залоченые записи.
DocAl
Дата: 12.09.2006 20:34:28
pamir

Если в mySQL конструкция for update работает также как в Оракле, то смотрим, что получится
Первый пользователь залочил строки. (update и commit еще не прошли)
В это время второй пользователь попытался их залочить..... Что тут произойдет? В оракле он будет ждать освобождения, т.к. не указана конструкция nowait. А здесь?
Если тоже ждать, то после того, как транзакция у первого пользователя завершится, второй, дождавшись, успешно залочит и поправит их под себя. Я не прав?

PS. Оракл не умеет пропускать залоченые записи.

Вы не могли бы привести решение этой задачи для оракла, в таком случае? Дело в том, что мне самому нередко нужен подобный механизм, и моя реализация, как очевидно и мне самому, далека от идеала.
DocAl
Дата: 12.09.2006 20:38:19
Единственное что хочу пояснить в своём варианте, другие юзеры, получающие данные этим же запросом, не смогут изменить те же строки до истечения таймаута, в данном случае, 20 минут. Что, впрочем, никак не гарантирует от изменения данных каким-либо другим способом, что, собственно, и является недостатком этого метода, как и его громоздкость.
miksoft
Дата: 12.09.2006 20:44:25
Погодите, а чего вы записи лочить взялись?
я не вижу, где написано у автора, что юзеры будут менять эти записи?

вопросы автору:
1) должны ли записи выдаваться строго подряд, т.е. без пропусков?
2) что делать, когда дошли до конца таблицы?
pamir
Дата: 12.09.2006 23:10:19
DocAl
Вы не могли бы привести решение этой задачи для оракла, в таком случае? Дело в том, что мне самому нередко нужен подобный механизм, и моя реализация, как очевидно и мне самому, далека от идеала.

Для начала четко определим условия.
Есть некоторый набор данных, упорядоченный по полю или нескольким полям. Необходимо при обращении к нему разных пользователей выдавать очередную порцию данных, продолжающих последовательность после выданных предыдущему пользователю.
1. Что делать, если данных не хватит на всех?
2. Если настал момент, когда никто не обращается к данным, то первый следующий пользователь получает данные сначала?
Так?
Все-таки интересно, для чего это нужно?

DocAl
Единственное что хочу пояснить в своём варианте, другие юзеры, получающие данные этим же запросом, не смогут изменить те же строки до истечения таймаута, в данном случае, 20 минут. Что, впрочем, никак не гарантирует от изменения данных каким-либо другим способом, что, собственно, и является недостатком этого метода, как и его громоздкость.

В оракле? В оракле второй пользователь дождется коммита у первого (т.е. его сессия будет висеть и ждать освобождения заблокированных строк), блокировка for update снимется вместе с коммитом, и никто не помешает ему залочить эти же самые строки не дожидаясь 20ти минут. Чтобы вторая сессия не ждала, нужно использовать конструкцию for update nowait, но в этом случае строки не пропускаются, а поднимается ошибка timeout, которую надо ловить и обрабатывать.
О! В процессе написания пришло одно решение, но оно не гарантирует последовательности выдаваемых данных
Вместо селекта открываем курсор и пытаемся лочить последовательно строки, обрабатывая ошибки по невозможности залочить и пропуская таким образом залоченные строки.
Проблема - может получиться "гребенка". Две сессии могут залочить строки через одну.
FatalQ
Дата: 13.09.2006 01:21:41
+
Смущает если будет очень много одновременных подключений и сервер "не успеет" обновить состояние.


Если сначала обновлять состояние, а потом брать данные, тогда успеет.
Примерно так
UPDATE ControlTable SET LastMsg = @LastMsg + 10 WHERE @LastMsg := LastMsg
SELECT ... LIMIT @LastMsg, 10
Гутманс
Дата: 13.09.2006 09:24:45
+
На сервере есть табличка, из которой берутся данные.
Задача в том, чтобы на каждый запрос, в случае "одновременного доступа" пользователей, всегда выдавались уникальные данные.

Другими словами, если юзер А получил с 1 по 10 запись из таблицы table, то юзер Б должен получить следующие 10 записей и никак иначе.

По идее сейчас реализовано, что как только юзер А получит свою порцию данных, то панель управления "запомнит" с какой записи дальше следует выполнять запрос.

Смущает если будет очень много одновременных подключений и сервер "не успеет" обновить состояние.

Ни разу не сталкивался с подобным, поэтому такой и вопрос.

Еретня! Я так понял юзерам надо получать последующую порцию данных?
Если так, то блин заведите глобальный счетчик и юзайте его все вместе. Нафига там что-то лочить?
+
Дата: 13.09.2006 10:08:22
Спасибо всем откликнувшимся! :)
Оказывается решение нетривиально как я вижу.

Теперь по порядку.

2 DocAl
Спасибо, иду читать ман.

miksoft
1) должны ли записи выдаваться строго подряд, т.е. без пропусков?
Да. Существует дополнительная таблица, в которой определяется "смещение". Оно собственно говоря и обновляется.

miksoft
2) что делать, когда дошли до конца таблицы?
Когда дошли до конца таблицы, то новый юзер просит данных, то мы отдаем ему ответ "нет данных". Это предусмотрено бизнес-логикой системы :)

pamir
2. Если настал момент, когда никто не обращается к данным, то первый следующий пользователь получает данные сначала? Так?
Ничего не происходит. В следующем обращении данные будут выдаваться относительно "смещения".

FatalQ
Если сначала обновлять состояние, а потом брать данные, тогда успеет.
По-моему это не совсем корректно, т.к. помимо выдачи данных выполняются еще некоторые действия, например чтение файлов.
Если в этот момент произойдет ошибка, то мы должны "красиво" сообщить об этом, залогировать ее и выйти из функции.

Соответственно статистика (состояние) не должна обновляться. Только в самую последнюю очередь.

Гутманс
Если так, то блин заведите глобальный счетчик и юзайте его все вместе. Нафига там что-то лочить?
И чем он будет отличаться от "смещения"? "Смещение" - это тоже своего рода счетчик. :)