За последние 24 часа нас посетили 54907 программистов и 1735 роботов. Сейчас ищут 847 программистов ...

Подсчёт мест в конкурсе

Тема в разделе "MySQL", создана пользователем Psih, 14 ноя 2009.

  1. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    почему тех и почему за ним, скорее того и перед

    выбрал нашего юзверя и того кто перед ним, по рейтингу(1й запрос); поменял местами, если рейтинг у нашего вдруг стал больше(2й запрос)
     
  2. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Mr.M.I.T.
    Делать COUNT(*) по условию - ну хз честно, как-то тормознуто вроде что-ли :)
     
  3. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Угу, а UPDATE явно быстрее.....
    ну-ну...
     
  4. akrinel

    akrinel Активный пользователь

    С нами с:
    26 янв 2009
    Сообщения:
    955
    Симпатии:
    1
    Адрес:
    Spb
    Я считаю что гадать на кофейной гуще в данном случае совершенно бессмысленный вариант,
    поэтому нужно просто "тупо" погонять эти варианты и решить что лучше.

    Дабы не быть совершенно бессмысленным капитаном очевидностью, я взялся за это дело.
    Итак.

    Тестировалось на таблице со структорой Psih на 1 000 000(один миллион) записей с небольшими изменениями:
    1) smallint, как-то не рулит(у нас же лям записей, для каждой записи свое место), пришлось заменить на INT.
    2) Добавил индекс по месту.


    Методом "от балды" я пришел к выводу, что нужно измерять:
    1) Время на пересчет мест(если оно есть).
    2) Время на запрос положения одного пользователя в таблице рейтинга.


    Вариант Psih'a:
    Пересчет мест:
    [sql]
    SET @place := 0;
    UPDATE `competition` SET `cmp_place` = @place := @place + 1 ORDER BY cmp_rating DESC;
    [/sql]
    Пересчет занял 19 минут.

    Запрос положения в рейтинге:
    [sql]
    SELECT `cmp_place` FROM `competition` WHERE `cmp_usr_id`=343223;
    [/sql]
    0,0007-0,0005 s

    Результаты EXPLAIN:
    [sql]
    select_type: simple
    table: competition
    type: const
    possible_keys: PRIMARY
    key: PRIMARY
    key_len: 4
    ref: const
    rows: 1
    [/sql]
    что в принципе логично.




    Вариант Mr.M.I.T.:
    Затраты на пересчет мест: 0

    Запрос положения рейтинга:
    Тут есть пара моментов спорных:
    1) Мы уже должны знать общий рейтинг пользователя.
    2) При наличии пользователей с одинаковым рейтингом, результат срабатывания неправилен, т.е. всем пользователям с рейтингом ноль в моем случае
    выдавалось милионное место, всем пользователям с рейтингом 99997, 31-е место и т.д. и т.п.
    Короче не работает.
    Чисто из академического интереса посчитаем:
    [sql]
    SELECT COUNT(*) FROM `competition` WHERE `cmp_rating`>=56748;
    [/sql]
    Скорость выполнения, разумеется, завист от того, на какой позиции находится пользователь.

    Так для "ТОП 100" выполнение запроса занимало: 0,45-0,55
    Для запроса выше 0,24
    Для ТОП 1000 лузеров -- 0,0012-0,0006
    Что не есть гуд (чаще всего ведь дергают юзверей с высоким рейтингом)

    Результаты EXPLAIN:
    [sql]
    select_type: simple
    table: competition
    type: range
    possible_keys: cmp_rating
    key: cmp_rating
    key_len: 4
    ref: NULL
    rows: 500252
    [/sql]


    Вариант Darevill:
    Как в варианте Mr.M.I.T. мы по умолчанию считали что уже "знаем" рейтинг пользователя, будем считать что тут мы изначально знаем его место

    Затраты на пересчет мест:
    [sql]
    -- Узнаем где он должен находится(при пользователях с одинаковым рейтингом нового запихиваем на самый верх, ну это уже как там Psih решит со своей бизнес логикой)
    -- 0.0009 s
    SELECT `cmp_place` FROM `competition` WHERE `cmp_rating`=18 ORDER BY `cmp_place` LIMIT 1;

    -- Сдвигаем всех (тестил для "взлета" на 10 позиций и для перемещения вверх на одну позицию: почему при любом раскладе упорно выдает 1-у секунду)
    -- После того как добавил индекс по плейс стало делаться за 0,2 s для 10-ти позиций и 0,06 для одной
    UPDATE `competition` SET `cmp_place`=`cmp_place`+1 WHERE `cmp_place` BETWEEN $up_place AND $down_place

    -- Перемещаем наверх "нового-лидера" 0.08s
    UPDATE `competition` SET `cmp_place`=$place WHERE `cmp_usr_id`=$id
    [/sql]
    Итого что-то около 0,2809-0,1409s
    Выборки также как у варианта Psih'a

    Итого:
    Вариант Psih не рулит совсем.
    Вариант Mr.M.I.T. выдает некорректные данные и среднюю производительность(для хайлоада непозволительно низкую).
    Вариант Darevill Оптимален на мой взгляд.


    Бонус трек:

    Последний вариант структуры таблицы:
    [sql]
    DROP TABLE IF EXISTS `test`.`competition`;
    CREATE TABLE `test`.`competition` (
    `cmp_usr_id` int(10) unsigned NOT NULL,
    `cmp_rating` int(10) unsigned NOT NULL,
    `cmp_place` int(11) NOT NULL default '0',
    PRIMARY KEY (`cmp_usr_id`),
    KEY `cmp_rating` (`cmp_rating`),
    KEY `cmp_place` (`cmp_place`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    [/sql]

    Скрипт которым забивались данные:
    PHP:
    1.  
    2. <?php
    3.  
    4.     require_once(dirname(__FILE__).'/goDB/classes/godb.php');
    5.    
    6.     $db   =  new  goDB('localhost', 'root', 'Li6tF5llonE', 'test');
    7.    
    8.    
    9.     $query  =  '';
    10.     $rowCount  =  0;
    11.  
    12.     for($i = 1; $i < 1000001; $i++){
    13.         ++$rowCount;
    14.         $query  .=  '('.$i.','.rand(0, 100000).',0), ';
    15.        
    16.         if($rowCount >= 1000){
    17.             insertRows($db, $query);
    18.             $rowCount  =  0;
    19.             $query  =  '';
    20.         }
    21.     }
    22.    
    23.     if($rowCount != 0){
    24.         insertRows($db, $query);
    25.     }
    26.    
    27.     function  insertRows($db, $query){
    28.         $db->query('INSERT INTO `competition`(`cmp_usr_id`, `cmp_rating`, `cmp_place`)
    29.                     VALUES '.rtrim($query, ' ,')); 
    30.     }
    31. ?>
    32.  

    Тестовая конфигурация:

    Процессор: AMD Athlon X2 Dual-Core QL-60 1.9 GHz
    RAM: 2Gb (хз какая, вроде как DDR 2)
    OS: Windows Vista Home Basic (на убунте из-за моей криворукости умерли Apache и MySQL и не переставляются, а ось пока руки не дошли переставить).


    P.S. MySQL фактически никак настроен не был(все настройки по дефолту).


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

    akrinel Активный пользователь

    С нами с:
    26 янв 2009
    Сообщения:
    955
    Симпатии:
    1
    Адрес:
    Spb
    Черт совсем забыл выразить свое бесценное мнение, я бы сделал все как в варианте Darevill, только запихал бы это дело в процедуру, куда передавал бы только id пользователя и кол-во баллов для зачисления, но делать сейчас процедуру и мерить сколько памяти она съест откровенно влом. :)
     
  6. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    akrinel
    накосячил.
    Поскольку не учел соотношения чтение/запись...

    Решение Mr.M.I.T.
    по отношению к Psih 19*60 / (0.55 -0.0007) = 2070
    Т.е. если на каждую операцию записи у Psih приходится 2070 чтений, то варианты Psih,Mr.M.I.T. эквиваленты.

    по отношению к Darevill 0,2809 / (0.55 - 0.0007) = 0.5
    Т.е. если на каждую операцию записи у Darevill приходится 0.5 чтений, то варианты Darevill,Mr.M.I.T. эквиваленты.

    Смущает вот это...
    Но сейчас проверю.

    P.S. скрипт с подключением твоей библиотеки наверняка замечательная вещь...
    Вот только библиотеки нет :)
     
  7. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    какие странные результаты для COUNT(*), проверим
    Ps. А как должно с нулевым рейтингом быть?
    Pss. Ты не учёл местного кеширования в моём варианте
     
  8. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    это типа если за юзверя не голосавали {
    выводим place из бд
    }иначе{
    считаем место
    записываем его в бд
    ставим флаг о том что скешировали
    }

    поэтому в топе проблем будет много меньше
     
  9. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    431703 за 0,2-0,17
    1000000 за 0,41

    Свой запрос, блин, в студию
    У меня получилось следующее
    Все ТОП 1000
    [sql]SELECT * FROM `competition` ORDER BY `cmp_rating` DESC LIMIT 0, 1000;[/sql]
    0,0101 -0,0086
    Все ТОП 1000 лузеров
    [sql]SELECT * FROM `competition` ORDER BY `cmp_rating` ASC LIMIT 0, 1000;[/sql]
    0,0424 -0,0086

    конкретный пользователь
    [sql]SELECT COUNT(*) FROM `competition` WHERE `cmp_rating`>99900[/sql]
    970е место 0,0012-0,0008 (таких пользователе 35 штук, второй запрос на получение пользователя 0.0014-0,0010)
    Итого 0,0026-0,0018

    [sql]SELECT COUNT(*) FROM `competition` WHERE `cmp_rating`>900[/sql]
    990883е место 0,39-0,38 (таких пользователе 35 штук, второй запрос на получение пользователя 0.0012-0,0007)
    Итого 0,3912-0,3807