Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > PHP: Базы Данных > Как взять id последнего сообщения с 100% гарантией


Автор: linuxoid 5.7.2010, 21:45
Здравствуйте коллеги! У меня такой вопрос: на сайте много пользователей, которые пишут сообщения. Если все ОК с валидацией, то заносим сообщение пользователя в базу. Затем мне нужен id этого сообщения из базы. Я просто беру max(id). Проблема следующая: если одновременно много пользователей добавляют сообщения, то по случайности может оказаться, что этот max(id) является id сообщения другого пользователя. Т.к. он успел написать новое сообщение и добавить его именно в тот момент, когда в базу добавлялось еще предыдущее (или любая другая причина). Т.е. по идее есть вероятность того, что я получу неверный id. Как нужно правильно сделать, чтобы я 100% был уверен, что id взят именно того сообщения, которое сейчас было вставлено. Нужно использовать транзакции? Если да, то можно ли оставить выборку max(id) как есть? Т.е. текущий код абсолютно тот же, но просто разместить все в одной транзакции. 
Благодарствую за внимание.

Автор: DimW 6.7.2010, 08:57
Цитата(linuxoid @  5.7.2010,  21:45 Найти цитируемый пост)
по идее есть вероятность того, что я получу неверный id

причем стопроцентная. 

Цитата(linuxoid @  5.7.2010,  21:45 Найти цитируемый пост)
Нужно использовать транзакции?

т.е. вы полагаете что при добавлении сообщений вы транзакции не используете?


Цитата(linuxoid @  5.7.2010,  21:45 Найти цитируемый пост)
Я просто беру max(id).

linuxoid, вы про автоинкрименты, генераторы, последовательности что нить слышали?

Автор: skyboy 6.7.2010, 10:23
укажи СУБД и целевой язык(Mysql+PHP, Delphi+Paradox, C#+SQL Server). быстрее получишь решение, привязанное к используемым технологиям.
а на абстрактный вопрос получишь абстрактный же ответ

Автор: linuxoid 6.7.2010, 10:52
База postgres или mysql. Все на php. Транзакции я не использую. Вот пример для Postgres'a.


Код

INSERT INTO messages(ref_users_id, ref_sections_id, email, phone, text, publication_date, ip) VALUES('14', '10', '[email protected]', '80100100', 'Test!!!', '1278402306', '130.50.28.33');
SELECT max(id) as max FROM messages LIMIT 1;

Далее с этим id я вставляю дальше в другие таблицы базы различные сведения.

INSERT INTO... (ref_messages_id, ...

Ничего кроме этого кода нет. Т.е. никаких транзакций, никаких инкрементов. Как поменять код, чтобы было бы грамотно и безопасно.

Автор: skyboy 6.7.2010, 11:41
что стоило разместить вопрос в разделе "РНР: БАзы данных"? теперь ждем модератора этого раздела
РНР. хорошо. а какие функции используются? mysql_query? PDO? ADOLite? для семейства функций mysql_* есть функция mysql_last_insert_idhttp://php.net/mysql_insert_id. в самой mysql есть функция http://dev.mysql.com/doc/refman/5.1/en/information-functions.html#function_last-insert-id(но я не помню - надо смотреть, как себя ведет функция в условиях нескольких подключений от имени одного пользователя; скорее всего, работает нормально, но все же лучше убедиться)
а в postgresql, вроде как, сначала получаешь значение sequence при помощи nextval, а потом уже вставляешь запись. при этом полученное значение будет точно уникальным.

Автор: linuxoid 6.7.2010, 12:55
Спасибо. Становится яснее, что нужно использовать более специализированные функции, которые конкретно связаны с инкрементом. Но как быть с "Далее с этим id я вставляю дальше в другие таблицы базы различные сведения.". Стоит ли мне ознакомиться с транзакциями поближе? Т.е. правильно ли я думаю, что нужно их использовать в моем случае.

P.S. В PHP использую только стандартные функции pg_ и mysql_

Автор: skyboy 6.7.2010, 13:33
транзакции - это другое.
Цитата(linuxoid @  6.7.2010,  11:55 Найти цитируемый пост)
Но как быть с "Далее с этим id я вставляю дальше в другие таблицы базы различные сведения."

получив идентификатор только что вставленной записи при помощи mysql_last_insert_id(), сформировать нужные тебе запросы с этим значением. в чем, собственно, проблема?

Автор: linuxoid 6.7.2010, 13:59
Цитата

транзакции - это другое.


Согласен.

Код

<?php
$link = mysql_connect('localhost', 'mysql_user', 'mysql_password');
if (!$link) {
    die('Could not connect: ' . mysql_error());
}
mysql_select_db('mydb');

mysql_query("INSERT INTO mytable (product) values ('kossu')");
printf("Last inserted record has id %d\n", mysql_insert_id());
?>



Извиняюсь за назойливость, но т.е. получается, что используя к примеру mysql_insert_id() исключается проблема, что в этот момент другой пользователь вставил сообщение и я могу случайно получить id другого сообщения - т.е. всегда будет именно нужный id? 

Автор: skyboy 6.7.2010, 14:34
Цитата(linuxoid @  6.7.2010,  12:59 Найти цитируемый пост)
т.е. всегда будет именно нужный id?  

да.

Автор: linuxoid 6.7.2010, 15:19
Ok, благодарю!

Автор: gcc 7.7.2010, 10:53
в Oracle есть по-моиму транзакция SELECT, чтобы выбрать 100% новую запись... (если это то, что надо)

Автор: gcc 7.7.2010, 11:18
Цитата

Когда пользователь находит запись, которую нужно изменить (или просто желает добавить запись в таблицу), то он нажимает кнопку добавления/редактирования и в появившемся диалоге заполняет/изменяет поля записи и затем сохраняет/отменяет редактирование.
Как же настроить транзакции для такого приложения?
Для запроса SELECT. ., который читает данные в сетку, следует использовать транзакцию с доступом "только для чтения" с уровнем изоляции READ COMMITED, чтобы получить самые "свежие" данные из таблицы, как только они будут обновлены/добавлены (не надо забывать о том, что наше приложение многопользовательское и одновременно могут работать несколько приложений). Примерный набор параметров такой:

read
read_committed 
rec_version 
nowait

При этом обеспечивается чтение всех подтвержденных другими транзакциями записей, причем без конфликтов с параллельно работающими пишущими и читающими транзакциями.
Такую транзакцию можно длительное время держать открытой - сервер не нагружается версиями записей.
Для запроса на изменение/добавление данных можно использовать транзакцию с уровнем изоляции concurrency. Запрос на обновление в этом случае должен быть очень коротким: пользователь заполняет необходимые поля, запускается транзакция, делается попытка выполнить запрос, и затем, если не возник н> конфликта на запись с другой транзакцией, подтверждение нашей транзакции или откат, если был конфликт (на уровне клиентского приложения конфлнмы проявляются в виде исключений, которые удобно отлавливать с помощью коп струкций try.. .except или try.. .catch)
Параметры такой транзакции будут следующими: 

write
concurrency
nowait

Такой набор параметров позволит нам сразу (nowait) выявить то, что запись редактируется/изменяется другим пользователем (возникнет ошибка), а также предотвратить попытки других пользователей начать изменение записей, трансформированных нашей транзакцией (у претендента возникнет ошибка "update conflict"). Надо отметить, что перед редактированием нужно перечитать запись, потому что она могла быть изменена, а в кеше сетки может все еще находиться старая версия
Для запросов, которые применяются для построения отчетов, однозначно нужно использовать транзакцию с режимом доступа "только для чтения" и с уровнем изоляции concurrency:


read
concurrency
nowait

Такая транзакция будет возвращать строго те данные, что существовали на момент ее запуска, - это очень важная особенность для отчетов, которые строятся за несколько проходов по базе данных.
Для запросов на чтение справочных данных можно использовать транзакцию, аналогичную запросу SELECT для выборки данных в сетку. 


http://www.realcoding.net/article/view/1581

Автор: krypt3r 8.7.2010, 13:28
для постгреса
Код

SELECT LASTVAL();

В php нет вроде функции, аналогичной mysql_insert_id()

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)