|
|
|
| Задача такая: подсчитать количество уникальных посетителей данной страницы, идентифицируя посетителя по его IP адресу. При этом уникальным считается посетитель, который или здесь не был вообще, или со времени прихода которого прошло более 5000 секунд (полтора часа).
В настоящее время я эту задачу решаю так.
- определяю IP посетителя и фиксирую астрономическое время его прихода в секундах;
- проверяю, есть ли в папке, относящейся к данной странице, текстовый файл с именем, совпадающим с IP;
- если такой файл есть, считываю его содержимое, которым является время записи этого файла в секундах;
- если такого файла нет, или если время его записи отличается от текущего более, чем на 5000 секунд, то посетитель считается уникальным. В этом случае формируется текстовый файл с именем, совпадающим с IP, содержимым которого является текущее астрономическое время в секундах. И этот новый файл записывается в папку, соответствующую данной странице сайта.
Как эту задачу решить с использованием БД?
Подготовка:
1. Создать таблицу с именем данной страницы.
2. Сформировать в этой таблице три колонки: IP, время в секундах, идентификатор.
Работа:
1. Узнать, есть ли в колонке IP запись, соответствующая IP посетителя. Как это сделать, каким запросом?
2. Если запись есть, то узнать ее идентификатор. Как?
3. Считать данные из колонки "Время" с этим идентификатором. Как?
4. Если записи с нужным IP нет, то на новую строку записать в таблицу IP адрес очередного посетителя, а в колонку Время текущее время в секундах.
5. Если время отличается от текущего больше, чем на 5000 секунд, то в строку Время этого идентификатора записать текущее время в секундах. Как это сделать?
Верно ли я рассуждаю?
Выгодно ли использовать для этого БД? | |
|
|
|
|
|
|
|
для: Владимир55
(17.10.2008 в 02:13)
| | >Верно ли я рассуждаю?
Нет. Посмотрите таблицы power counter (Вы же его используете, если не путаю) - там все это уже проделано. | |
|
|
|
|
|
|
|
для: Loki
(17.10.2008 в 09:41)
| | Не могли бы Вы пояснить словами, что не так?
===========================================
За рекомендацию опираться на таблицу power counter спасибо - я этого сделать не догадался! Однако это иллюстрация пока что слишком сложна для меня. | |
|
|
|
|
|
|
|
для: Владимир55
(17.10.2008 в 02:13)
| | 1. Создать таблицу в которой хранить номер страницы и путь к ней
id_page name
2. Создать таблицу хитов (где putdate - время, ip - адрес, а id_page - номер страницы)
id ip id_page putdate
А лучше ещё и идентификатор сессии (дополнительное поле session) кладите в базу данных. Тогда задача подсчета уникальных посетителей сведётся к запросу
SELECT COUNT(DISTINCT session) FROM tbl
|
| |
|
|
|
|
|
|
|
для: Владимир55
(17.10.2008 в 02:13)
| | >Выгодно ли использовать для этого БД?
Это всегда выгодно хотя бы по тому, что СУБД работает на порядок быстрее файловых операций PHP (который мало того что интерпретатор, так ещё один из самых медленных). Кроме того, если вы не виртуоз при работе с блокировками - рано или поздно вы либо файлы побьете, либо информацию потеряете - так как по уму нужно создавать очередь... Т.е. если с файлами корректно и правильно работать - как раз и получается СУБД - зачем её писать на PHP, когда она на C уже написана. | |
|
|
|
|
|
|
|
для: Владимир55
(17.10.2008 в 02:13)
| | Подготовка
если учесть, что идентификатором у Вас является сам IP, отдельную колонку с ним создавать вроде как незачем.
CREATE TABLE mycounter
(
ip char(20),
hittime INT(11),
PRIMARY KEY(ip)
);
|
Работа:
<?
$addr = $_SERVER['REMOTE_ADDR'];
$time = time();
$sql = "SELECT * FROM mycounter WHERE ip = '$addr'";
if(($res = mysql_query($sql)) == 0) //1
exit( "Error in $sql : ". mysql_error() );
$row = mysql_fetch_assoc($res); //3
if(!$row //2
|| intval($row['hittime']) +5000 < time()) //3, 5
{
$sql = "REPLACE INTO mycounter (ip, hittime) VALUES ('$addr', $time)";
if(($res = mysql_query($sql)) == 0) //4,5
exit( "Error in $sql : ". mysql_error() );
}
|
в комментариях помечены примерные пункты Вашего плана. | |
|
|
|
|
|
|
|
для: Trianon
(17.10.2008 в 11:29)
| | для: Хеопс
для: Trianon
Большое-большое спасибо, это чрезвычайно интересно и очень полезно!
Но (чисто для понимания) хочу спросить: тот алгоритм, который я описал, просто неоптимален, или его с базой вообще нельзя осуществить (т.е. он принципиально неверен)?
Какой элемент в нем неосуществим (что бы в будущем мне не действовать в этом направлении)? | |
|
|
|
|
|
|
|
для: Владимир55
(17.10.2008 в 14:45)
| | Можно осуществить, почему нельзя.
Написать код, строго соответствующий Вашим пунктам?
Трудно сказать, будет он неоптимальным или неверным.
Вот представьте такую ситуацию.
Два запроса с нового клиента на сервер пришли практически одновременно.
Ну и примерно параллельно выполняются.
Что станет с Вашим алгоритмом на пункте 4?
Это неверное или неоптимальное поведение?
Вы, вероятно, назовете неоптимальным.
Я - неверным.
Cheops, вероятно, скажет, что в скрипте присутствуют корявки.
PS. Это не потому что скрипт Ваш, а не мой.
Мой наверняка тоже неверный. | |
|
|
|
|
|
|
|
для: Trianon
(17.10.2008 в 15:28)
| | "PS. Это не потому что скрипт Ваш, а не мой. "
Я это даже в голове не держу...
"Что станет с Вашим алгоритмом на пункте 4?"
Родится ошибочный результат. И это плохо. Но ведь и в Вашем случае всё также?
"Написать код, строго соответствующий Вашим пунктам? "
Да, мне этого бы очень хотелось! | |
|
|
|
|
|
|
|
для: Владимир55
(17.10.2008 в 15:58)
| | >>"Что станет с Вашим алгоритмом на пункте 4?"
>Родится ошибочный результат. И это плохо. Но ведь и в Вашем случае всё также?
Глядите внимательно. В моем случае двух записей с одним ip создано не будет.
>>"Написать код, строго соответствующий Вашим пунктам? "
>Да, мне этого бы очень хотелось!
Пожалуйста.
Попытался максимально точно следовать пунктам.
Пункт пятый переехал из-за явной ошибки логики. Грех было не переставить.
<?php
//Подготовка:
//1. Создать таблицу с именем данной страницы.
//2. Сформировать в этой таблице три колонки: IP, время в секундах, идентификатор.
/*
CREATE TABLE mycounter
(
id INT(11) NOT NULL DEFAULT 0 AUTO_INCREMENT,
ip char(20),
hittime INT(11),
PRIMARY KEY(id)
);
*/
//Работа:
$addr = $_SERVER['REMOTE_ADDR'];
$time = time();
//1. Узнать, есть ли в колонке IP запись, соответствующая IP посетителя.
if(mysql_result(mysql_query("SELECT COUNT(ip) FROM mycounter WHERE ip = '$addr'"), 0))
{
//2. Если запись есть, то узнать ее идентификатор.
$id = mysql_result(mysql_query("SELECT id FROM mycounter WHERE ip = '$addr'"), 0);
//3. Считать данные из колонки "Время" с этим идентификатором.
$hittime = intval(mysql_result(mysql_query("SELECT hittime FROM mycounter WHERE id = $id"), 0));
//5. Если время отличается от текущего больше, чем на 5000 секунд,
if($hittime +5000 < $time)
// то в строку Время этого идентификатора записать текущее время в секундах.
mysql_query("UPDATE mycounter SET ip = '$addr', hittime = $time WHERE id = $id");
//4. Если записи с нужным IP нет,
}
else
{
// то на новую строку записать в таблицу IP адрес очередного посетителя,
// а в колонку Время текущее время в секундах.
mysql_query("INSERT INTO mycounter (ip, hittime) VALUES ('$addr', $time)");
}
|
| |
|
|
|
|
|
|
|
для: Trianon
(17.10.2008 в 16:48)
| | Спасибо, все это чрезвычайно интересно!
Буду думать и пробовать.
Ну, ОЧЕНЬ интересно! Кодомания... | |
|
|
|
|
|
|
|
для: Владимир55
(17.10.2008 в 17:22)
| |
Почему для адреса отводится 20 знаков?
Разве шестнадцати недостаточно?
Или в адресе есть какие-нибудь невидимые служебные биты? | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 10:38)
| | IP-адрес -- это число. Хранить его в текстовом поле нецелесообразно. | |
|
|
|
|
|
|
|
для: BinLaden
(05.02.2009 в 11:09)
| | только это число, которое в тип данных php нормальным образом не укладывается. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 11:27)
| | А где оно будет фигурировать в PHP-скрипте в качестве числа? | |
|
|
|
|
|
|
|
для: BinLaden
(05.02.2009 в 11:43)
| | нигде не будет?
Тем более. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 11:54)
| | Я Вас не понимаю ваще. | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 10:38)
| | Для IPv4 достаточно 15.
И даже меньше, если начать упаковывать. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 11:26)
| | Просто для лучшего понимания: набросить в поле 3-5 лишних знаков "просто так" - это нормально? На практике гуру так поступают? | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 13:13)
| | мне просто было лень считать все эти циферки. В 20 входило. В 10 - нет.
кроме того, я точно знаю, что в перспективе это не спасет, поскольку адрес IPv6, выглядящий примерно как 2001:471:1f11:251:290:27ff:fee0:2093 туда всяко не полезет.
Вообще же если смотреть на выбор поля для IP с позиции диапазонного поиска, то хранить его придется совершенно не так. Придется его дополнять до полной ширины, при этом захочется наверняка место поэкономить....
Короче, факторов много. Вам же нужен был пример простой и понятный. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 13:23)
| | С примером - да. За пример спасибо.
Но я сейчас думаю о полях вообще, на разные случаи жизни. Составляю общую таблицу для занесения всей будущей статистики, и думаю над каждым полем, ибо суммарно уже набежало 350 знаков в полях на 9 столбцов. То ли сделать "впритык" по разумным потребностям, то ли набросить в каждое поле несколько знаков на всякий случай.
Как принято у спецов?
Резерв делается? Или каждый разряд лишний тормоз? | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 13:53)
| | неужели все эти столбцы у Вас фиксированной ширины? | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 13:54)
| | В смысле, будут ли они заполнены полностью на всю ширину отведенного поля? В основном будут заполнены, хотя возможны и пропуски.
Или в понятие "фмксированная ширина" вложен другой смысл?
Я задаю поля так:
$query = "CREATE TABLE log
(
id INT (11) NOT NULL AUTO_INCREMENT,
time_mks INT (18),
ip CHAR (16),
str_vh CHAR (24),
p_s CHAR (16),
p_zapros CHAR (200),
rekl_set CHAR (24),
namerekl CHAR (16),
str_prod CHAR (24),
PRIMARY KEY(id)
)";
|
| |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 14:10)
| | Возьмем это вот поле p_zapros CHAR (200)
У Вас каждый запрос будет длиной ровно в 200 символов?
И Вас не беспокоит, что в каждой записи таблицы остаток поля будет бесполезно забит пробелами? | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 14:37)
| | "Возьмем это вот поле p_zapros CHAR (200)
У Вас каждый запрос будет длиной ровно в 200 символов?"
Это поле под поисковый запрос. Посмотрел свою статистику - а основном, 30-60 знаков, максимум 180 знаков.
"И Вас не беспокоит, что в каждой записи таблицы остаток поля будет бесполезно забит пробелами?"
Беспокоит. Собственно, это беспокойство и продиктовало вопрос.
Но я же не знаю, как оно на самом деле: то ли пробелы никаких ресурсов не потребляют, то ли для моей таблицы их потребление вообще незначительно, а может огромно? | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 14:51)
| | Загляните при остановленном сервере в файл с данными таблицы (log.myd) и увидите, что ими-то в основном файл и наполнен. Процентов на 70-80 согласно Вашей статистике.
Вывод - для этого поля куда более подходит тип с переменной длиной - VARCHAR (200)
поменяв тип у поля можно опять остановить сервер и снова заглянуть в файл. Картина будет совсем другой.
Да и сам файл похудеет изрядно. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 14:59)
| | Да, это мудро!
А если поля не хватает, то информацию обрезают с какой стороны? Надеюсь, начало записывается в любом случае? | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 15:08)
| | >А если поля не хватает, то информацию обрезают с какой стороны?
Поскольку длина в VACHAR на распределение пространства не влияет, то делать её нужно такой, чтоб поле помещалось без опасения быть обрезанным.
На вопрос легко получить ответ, поставив эксперимент.
Кстати, в режиме strict mode сервер не даст записать значение, которое не помещается в поле, и вернет ошибку при такой попытке.
И еще раз напомню - стоит в таблице появиться хоть одному полю переменной длины - тип CHAR теряет смысл. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 15:24)
| | "стоит в таблице появиться хоть одному полю переменной длины - тип CHAR теряет смысл."
То есть, если сейчас у меня
$query = "CREATE TABLE log
(
id INT (11) NOT NULL AUTO_INCREMENT,
time_mks INT (18),
ip CHAR (16),
str_vh CHAR (24),
p_s CHAR (16),
p_zapros VACHAR (200),
rekl_set CHAR (24),
namerekl CHAR (16),
str_prod CHAR (24),
PRIMARY KEY(id)
)";
|
то правильнее сделать
$query = "CREATE TABLE log
(
id INT (11) NOT NULL AUTO_INCREMENT,
time_mks INT (18),
ip VACHAR (200),
str_vh VACHAR (200),
p_s VACHAR (200),
p_zapros VACHAR (200),
rekl_set VACHAR (200),
namerekl VACHAR (200),
str_prod VACHAR (200),
PRIMARY KEY(id)
)";
|
Так? | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 15:45)
| | почему двести-то?
Да, на распределение памяти это число не влияет.
Но начни Вы создавать индексы на полях, и настанет кошмар. Зачем Вам в индексном файле элементы в 200 байт размером?
Есть разумный предел исходя из смысла поля - следует ставить его.
Если такого предела нет, поле - хороший кандидат на массивные типы longtext, longblob и т.п.
Само собой, индексы на таких полях не строятся вообще (если не считать индексов полнотекстового поиска) | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 15:51)
| | "Но начни Вы создавать индексы на полях, и настанет кошмар."
Да ту и без индексов... :)))
Вот при такой ситуации
p_zapros VACHAR (200),
rekl_set CHAR (24),
| как понимать, что CHAR теряет смысл? | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 15:59)
| | смысл фиксированных полей только в том, чтобы сделать фиксированную длину всей записи в таблице.
Достаточно одного (любого) поля переменной длины, как запись тоже становится переменной длины. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 16:04)
| | Это я уже понял, а что происходит с CHAR (24)?
Да ничего? просто исполняется обычным образом? И если здесь 24 достаточно и больше не надо, то пусть так и будет CHAR (24)?
Верно или нет? | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 16:16)
| | просто нет никаких причин не заменить его на VARCHAR(24)
И что-то мне подсказывает, что в каких-то случаях MYSQL сам такую процедуру выполняет. Но тут могу и наврать.
Обратную вещь - замену VARCHAR (1) , VARCHAR(2), VARCHAR(3) на соотв. CHAR он делает точно. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 16:18)
| | "просто нет никаких причин не заменить его на VARCHAR(24)"
Вот этот вопрос меня и мучил.
"И что-то мне подсказывает, что в каких-то случаях MYSQL сам такую процедуру выполняет."
Как всегда, Вы правы!
Из мануала:
В некоторых случаях MySQL без уведомления изменяет определение столбца, заданное командой CREATE TABLE (Это может осуществляться также для команды ALTER TABLE):
Столбец VARCHAR с длиной меньше, чем четыре, преобразуется в столбец CHAR.
Если некоторый столбец в таблице имеет переменную длину, то и вся строка в результате будет переменной длины. Следовательно, если таблица содержит любые столбцы переменной длины (VARCHAR, TEXT или BLOB), то все столбцы CHAR с длиной, превышающей три символа, преобразуются в столбцы VARCHAR. | |
|
|
|
|
|
|
|
для: Владимир55
(05.02.2009 в 15:45)
| | Владимир55, и всё-таки лучше будет IP хранить в unsigned int, записывать и считывать через INET_ATON() и INET_NTOA() соотвественно. Тогда IPv4-адреса будут занимать в базе всего по 4 байта, а не 15. | |
|
|
|
|
|
|
|
для: BinLaden
(05.02.2009 в 16:20)
| | Абсолютно верно сказано. Не хватает лишь примера. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 16:21)
| | По-моему, Вы совсем недавно занимали другую позицию. Дескать, "это число, которое в тип данных php нормальным образом не укладывается". Причём тут PHP я, к сожалению, так и не понял. | |
|
|
|
|
|
|
|
для: BinLaden
(05.02.2009 в 16:23)
| | Ключевое слово "пример".
Оно же и в теме топика фигурирует.
Потому и. | |
|
|
|
|
|
|
|
для: Trianon
(05.02.2009 в 16:32)
| | Да я и так много сказал. | |
|
|
|
|
|
|
|
для: BinLaden
(05.02.2009 в 16:44)
| | Даже слишком. | |
|
|
|