Форум: Форум PHPФорум ApacheФорум Регулярные ВыраженияФорум MySQLHTML+CSS+JavaScriptФорум FlashРазное
Новые темы: 0000000
C++. Мастер-класс в задачах и примерах. Авторы: Кузнецов М.В., Симдянов И.В. Самоучитель MySQL 5. Авторы: Кузнецов М.В., Симдянов И.В. Программирование. Ступени успешной карьеры. Авторы: Кузнецов М.В., Симдянов И.В. PHP на примерах (2 издание). Авторы: Кузнецов М.В., Симдянов И.В. MySQL на примерах. Авторы: Кузнецов М.В., Симдянов И.В.
ВСЕ НАШИ КНИГИ
Консультационный центр SoftTime

Форум MySQL

Выбрать другой форум

 

Здравствуйте, Посетитель!

вид форума:
Линейный форум Структурный форум

тема: Несколько добавленных записей подряд
 
 автор: Fractured   (17.08.2009 в 02:32)   письмо автору
 
 

Ситуация следующая. Есть чат, в нём есть так называемые боты, т.е. в чат добавляются при каких-то событиях сообщения от системы. Некоторые боты ожидают, пока не истечёт время, т.е. пока разность стартовой метки времени и текущей не превысит, скажем, 5 минут. Но поскольку в чате посетителей довольно много, то относительно часто возникает ситуация, когда бот повторяет одно и то же сообщение 2 раза, то есть до того, пока метка времени не обновилась "срабатывает" сразу несколько раз условие в параллельно вызванных скриптах.

Очень кратко:

1. Происходит SELECT last_posting_date, ... FROM bots ...

В зависимости от last_posting_date бот либо выдаёт новое сообщение, либо молчит.

2. Если с момента последнего постинга прошло более пяти минут (time() - last_posting_date > 300), то обновляется last_posting_date в соответствующей боту запись в таблице bots. И после этого происходит добавление сообщения.

Очевидно, что иногда происходит почти одновременная выборка last_posting_date (первый пункт) и в двух выполняющихся скриптах выполняется условие time() - last_posting_date > 300 со всеми вытекающими!

Как лучше поступить в данной ситуации? Может надо включить режим транзакции до пункта 1 и выключить его после пункта 2 (тогда нужен InnoDB?)?
Завтра так и попробую, но может быть кто поправит меня. Я примерно представляю режим транзакции таким образом (грубо), что все InnoDB-таблицы внтури блокируются (или отдельные записи в них - не знаю), выполняются все запросы, записываются и только после даётся возможность другим достучаться до табличек.

   
 
 автор: Trianon   (17.08.2009 в 10:51)   письмо автору
 
   для: Fractured   (17.08.2009 в 02:32)
 

По-моему, достаточно механизма mysql_insert_id .

$lpd1 = SELECT MAX(last_posting_date), ... FROM bots ...
если (time() - $lpd1 > 300)
{
    $id = INSERT INTO bots 

    $lpd2 = SELECT MAX(last_posting_date) FROM bots WHERE id < $id

  [  или даже  $lpd2 = SELECT last_posting_date FROM bots WHERE id = 
                               (SELECT MAX(id) FROM bots WHERE id < $id)  ]

    если (time() - $lpd2 > 300)
    {
         выпустить сообщение
    }
    иначе DELETE FROM bots where id = $id
}

   
 
 автор: Fractured   (21.08.2009 в 18:14)   письмо автору
 
   для: Trianon   (17.08.2009 в 10:51)
 

А это нормальная практика? В моём случае многовато кода выходит, хотя вариант.

   
 
 автор: Trianon   (22.08.2009 в 00:22)   письмо автору
 
   для: Fractured   (21.08.2009 в 18:14)
 

а-я предложил красивый ход - уникальным ключ, формируемый путем округления таймштампа.

   
 
 автор: Fractured#   (27.09.2009 в 23:14)   письмо автору
 
   для: Trianon   (22.08.2009 в 00:22)
 

Вновь возвращаюсь к этой теме :confused:

Такой ход не подходит. Я не совсем раскрыл суть проблемы.

Бот задаёт какой-то вопрос. Пользователи отвечают. Иногда он задаёт подряд 2 вопроса. И всё это очень походит на борьбу со следствием, а не причиной. Причём Ваш вариант тоже, если честно...

Т.е. причина: начинается смена стадии этой викторины (стадий несколько: инициализация вопроса, подсказка, завершение в случае истечения времени) параллельно в 2-х скриптах. race condition...

По совету а-я получится так: бот задаст какой-то вопрос (т.е. идентификатор вопроса сохранится в определенном месте), он появится в чате. Но другой скрипт перезатрёт идентификатор вопроса, но в чате новый вопрос не появится.

   
 
 автор: Trianon   (27.09.2009 в 23:27)   письмо автору
 
   для: Fractured#   (27.09.2009 в 23:14)
 

в моем варианте я не вижу никакого перезатирания .
в вапранте а-я, очевидно, слегка придется доточить логику.

   
 
 автор: Fractured#   (27.09.2009 в 23:28)   письмо автору
 
   для: Trianon   (27.09.2009 в 23:27)
 

В Вашем - да. Просто он не очень нравится... OK, я ещё поищу другие способы.

   
 
 автор: Fractured#   (29.09.2009 в 17:36)   письмо автору
 
   для: Fractured#   (27.09.2009 в 23:28)
 

Всё-таки решается по-нормальному средствами MySQL (тип таблицы - InnoDB):

<?php

mysql_query
('SET `autocommit` = 0;');
mysql_query('START TRANSACTION;');
mysql_query('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;');
mysql_query('SELECT `last_posting_date` FROM ... FOR UPDATE'); 

if( 
time() - $last_posting_date 300 )
{
    
mysql_query('UPDATE ... SET `last_posting_date` = ' time() . ';');

    
# Добавление поста и т.д.
}

mysql_query('COMMIT;');

?>

   
 
 автор: Valick   (29.09.2009 в 18:04)   письмо автору
 
   для: Fractured#   (29.09.2009 в 17:36)
 

InnoDB медленнее MyISAM
Транзакции нужны грубо говоря из-за возможности отката, что вы собираетесь откатывать в чате?
Того же можно добиться с помощью блокировки таблиц, хотя нужна она тут или нет я сказать не возьмусь, уж очень непонятно вы всё обьяснили.
Вы случайно не WoW ковыряете?

   
 
 автор: Fractured#   (29.09.2009 в 23:15)   письмо автору
 
   для: Valick   (29.09.2009 в 18:04)
 

Выражусь культурнее: спасибо, мне не нужна блокировка всей таблицы сразу.

   
 
 автор: Valick   (30.09.2009 в 00:12)   письмо автору
 
   для: Fractured#   (29.09.2009 в 23:15)
 

модератор удалил вместе с Вашим постом мой последний дебильный совет, о том что блокировка (если она нужна, в чём лично я сильно сомневаюсь) раз в 5 минут - это лучше, чем ежесекундный проигрышь в скорости при использовании InnoDB (тем более что у вас такой нагруженный чат)

   
 
 автор: Fractured#   (30.09.2009 в 01:01)   письмо автору
 
   для: Valick   (30.09.2009 в 00:12)
 

Ваш способ предполагает блокировку каждый раз, в противном случае это никак не поможет. И вообще, скажу Вам по секрету, InnoDB вообще-то при интенсивных запросах к таблице (SELECT/INSERT/DELETE/UPDATE) на нагруженных проектах предпочтительнее, нежели MyISAM.

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

Если Вы до сих пор не понимаете в чём проблема, то самостоятельно смоделируйте ситуацию:

script1.php
<?php

$db
->query('SELECT `timestamp` FROM ...');

if( 
$db->result() < time() )
{
    
sleep(25);

    
$db->query('UPDATE ... SET `timestamp` = ' . ( time() + 60 ));
    
$db->query('INSERT INTO test_table (page_num) VALUES(1)');
}

?>


script2.php
<?php

$db
->query('SELECT `timestamp` FROM ...');

if( 
$db->result() < time() )
{
    
$db->query('UPDATE ... SET `timestamp` = ' . ( time() + 60 ));
    
$db->query('INSERT INTO test_table (page_num) VALUES(2)');
}

?>


Сначала запустите script1.php, затем script2.php. Задача состоит в том, чтобы в test_table оказалась лишь одна запись: 1

P.S.
Я надеюсь Вы не начнёте придираться к этому коду: это скорее псевдокод, просто показана логика. Оформите запросы, структуры таблиц как Ваша душа пожелает.
P.P.S. Да, про чат и ботов я зря, извиняюсь. Надо было сразу как-то абстрагироваться...

   
 
 автор: Valick   (30.09.2009 в 10:29)   письмо автору
 
   для: Fractured#   (30.09.2009 в 01:01)
 

В MySQL предусмотрена возможность создания таблиц без транзакций, что необходимо приложениям, требующим максимально возможной скорости работы. Эти таблицы могут храниться в памяти, относиться к типу HEAP-таблиц или дисковых MyISAM.

Блокировка таблиц, применяющаяся в нетранзакционных таблицах MyISAM, во многих случаях работает быстрее, нежели блокировки на уровне страниц, строк или контроль версий. Недостаток этого подхода в том, что если не учитывать механизм работы блокирования таблиц, один длительный запрос может надолго заблокировать таблицу. Обычно этого эффекта можно избежать, приняв соответствующие меры при разработке приложения. Если это не удастся, всегда можно изменить тип таблицы и сделать ее транзакционной.
_____
http://www.mysql.ru/docs/man/
возможно информация несколько устарела, спорить не буду
_____
и ещё раз повторюсь, что понятия не имею что вы там творите, но для того чтобы вставить сообщение от бота в чат скорее всего вообще не нужна никакая блокировка таблиц...
погуглил по поводу не родного (!) для MySQL типа таблиц InnoDB сообщения похожи друг на друга как "диктанты первоклассников" что настораживает.
Но грубо говоря, если Вас устраивает полученный результат, то и дискутировать собсно не о чем.

   
 
 автор: Fractured#   (30.09.2009 в 14:05)   письмо автору
 
   для: Valick   (30.09.2009 в 10:29)
 

Я понимаю Ваше желание как-то оправдаться и представить меня глупым человеком. Именно поэтому Вы решили проигнорировать мою новую, абсолютно чёткую формулировку, в посте от 30.09.2009 в 01:01. Хорошо, не можете найти решение - не надо, мне оно не требуется (оно требуется Вам, чтобы в дальнейшем не говорить лишнего).

   
 
 автор: а-я   (17.08.2009 в 11:29)   письмо автору
 
   для: Fractured   (17.08.2009 в 02:32)
 

может
добавить еще одно поле в таблицу с сообщениями
`bot_time` INT NULL DEFAULT NULL

дать ему уникальный ключ.
UNIQUE (`bot_time`)

если комнат несколько то
UNIQUE (`room`, `bot_time`)

и записывать примерно так:

<?
$bot_time 
300// 5 мин

$bot_time floor(time() / $bot_time); // получаем сколько целых частей

// запрос
$sql '
INSERT IGNORE INTO 
 `tbl` 
SET 
 `bot_time` = '
.$bot_time.',
 `nick` = "Бот",
 `msg` = "'
.$msg.'"
'


?>


уникальный ключ - не даст повтора,
IGNORE - не вернет ошибку запроса.
NULL - для обычных сообщений юзеров

   
Rambler's Top100
вверх

Rambler's Top100 Яндекс.Метрика Яндекс цитирования