Как решить проблему с конкуренцией запросов на чтение в PHP?

На страницу сайта одновременное заходит большое количество людей.
На этой же странице есть, к примеру, учет посещений:

$mysql = mysql_fetch_array(mysql_query(селект));

mysql_query("UPDATE `page` SET `views` = `views` + '1' WHERE `status` = 'on' AND `id` = '1';");

// если превышен лимит просмотров страницы
if ($mysql['views'] >= 10) 
{
    mysql_query("UPDATE `page` SET `status` = 'off' WHERE `id` = '1';"); // выключаем доступность страницы

    echo 'Исчерпан лимит просмотров';
}


При малом количестве пользователей, открывших эту страницу, проблемы не возникает и при 10 просмотрах страница переходит в статус off.
Но если на страницу зайдут 100, 500 человек и более, то условие срабатывает запоздало и страница может перейти в статус off, к примеру, после 15 просмотров, а то и больше, хотя в условии строго задано 10.

Как с этим бороться?
  • Вопрос задан
  • 2775 просмотров
Пригласить эксперта
Ответы на вопрос 7
FanatPHP
@FanatPHP
Чебуратор тега РНР
Для начала, вопрос получает приз за самый нерелевантный заголовок. Конечно же, дело в скорости работы условных операторов в РНР.

А тебе нужна блокировка на чтение
START TRANSACTION;
SELECT views FROM page where id=1 FOR UPDATE;

Заблокирует тебе строку на чтение и запись. Дальше можешь ее апдейтить и коммитить. После этого следующий, кто успеет схватить блокировку, заставить остальных ждать. И так далее.

Господь, жги.
Эту толпу обезьян уже не спасти.
Ответ написан
MrLoki
@MrLoki
I will.
Никак. Вы думаете о параллельной среде как об однопоточной. Можете принудительно пускать юзверей в один поток, тогда такой проблемы не возникнет.

Иначе алгоритм выглядит так:
b51629f9d4a34e85a4bbd5173a7ba794.png
Те пользователи что зашли и получили данные в промежутке пока «десятый» получил данные и закрыл страницу, тоже дойдут до конца и сохранят свои данные.

Чтобы пустить пользователей в один поток на запись, можете лочить таблицу между получением данных и записью инкремента, или использовать кэш для счётчиков, там это всё будет быстрее, но суть та же — дождаться пока один пользователь завершит сессию прежде чем пускать другого.
Ответ написан
А можно задать вопрос: что даст ограничение? Ведь сайт создан что бы на него заходили люди. И если на него хотят зайти 100500 человек одновременно это же прекрасно! Зачем их ограничивать?
Ответ написан
AxisPod
@AxisPod
И откуда берется $mysql из воздуха?
Ответ написан
У вас таблица page в каком формате? InnoDB или MyISAM?
Ответ написан
@Billy_Milligan
А почему не сделать через сессию?

if($_SESSION["count"] > 9) {
    echo "пошёл вон от сюда";
    exit;
}

$_SESSION["count"]++;


или если хотите с БД через глобалс?

$result = mysql_query("SELECT `status` FROM `page` WHERE `id` = '1';");

if($result['status'] == "off") {
    echo "пошёл вон от сюда";
    exit;
} else if($GLOBALS["count"] > 9) {
    mysql_query("UPDATE `page` SET `status` = 'off' WHERE `id` = '1';");
    unset($GLOBALS["count"]);
    echo "пошёл вон от сюда";
    exit;
}

$GLOBALS["count"]++;
Ответ написан
@ollisso
mysql_query("UPDATE `page` SET `views` = `views` + '1',status=IF(views>=10,'off','on') WHERE `status` = 'on' AND `id` = '1' and views < 10);

$changed= mysql_affected_rows();

if($changed){
// пускаем 
}else{
mysql_query("UPDATE `page` SET `views` = `views` + '1'  WHERE `status` = 'on' AND `id` = '1');
// закрыто
}


Т.е. основная идея:
1. обновляем счётчик , но только если мы можем увидеть страницу
2. сразу же проверяем, сработал ли апдейт. Если сработал- то ок, если не сработал, то значит:
1. заход не подсчитан, надо добавить счётчик.

Если вам не нужно считать тех кто не зашёл - то не добавляйте второй апдейт.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы