немного оптимизации или "Блок кто в сети" из ядрёного модуля user

Главные вкладки

Аватар пользователя Demimurych Demimurych 9 января 2010 в 16:42

Вот кусок кода из user_block
отвечающий за составления списка пользователей онлайн.

$interval = time() - variable_get('user_block_seconds_online', 900);

          // Perform database queries to gather online user lists.  We use s.timestamp
          // rather than u.access because it is much faster.
          $anonymous_count = sess_count($interval);
          $authenticated_users = db_query('SELECT DISTINCT u.uid, u.name, s.timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.timestamp >= %d AND s.uid > 0 ORDER BY s.timestamp DESC', $interval);

Смотрим на запрос и думаем - а какого Художника был просто не сделан запрос к таблице users с условием по полю acceess

 $authenticated_users = db_query('SELECT u.uid, u.name, u.access  FROM {users} u WHERE u.access >= %d  ORDER BY u.access DESC', $interval);

обращаем внимание на комментарий выше, где нам пытаются обьяснить:
мол использование индекса из таблицы session это значительно быстрее чем использование чистой таблицы users и поля access

Мы не поверим автору и загляним в EXPLAIN
вводные
таблица SESSION 37МБ
таблица users 103МБ

О ЧУДО
Автор этого запроса явно МНОГО КУРИЛ БАМБУКА а не православного канабиса.

даже невооруженным глазом видно, что использование таблицы session не просто МАРАЗМ а еще и КРАЙНЕ ВРЕДНЫЙ МАРАЗМ
о чем ВОПИТ Using where; Using temporary; Using filesort

Попробуем все таки понять как ТАКОЕ могло попасть в ядро
Когда, автор проводил свои исследования, он
а) сидел практически с пустой таблицой session
б) даже не удосужился посмотреть EXPLAIN обоих запросов.

ошибка в общем то не критичная, в случае если есть и правильно работает кеширование mysql (возможна схожая ситуация и на других БД)
разница на обоих запросах
на оригинальном варианте 0.017 Ms
на правильном 0.005 Ms

если же кеширования НЕТ то оригинальный запрос может выполняться ощутимо дольше.
из за необходимости создания временной таблицы и файловых операций.

UPD: Как справедливо заметил

"<a href="mailto:andypost@drupal.org">andypost@drupal.org</a>" wrote:
А как иначе вывести, что под одним логином несколько человек на сайте?

мой запрос с этим не справится.

В случае если такой функционал необходим, то модифицируем оригинальный запрос таким образом, чтобы избавится от файлсорта и временных таблиц убирая из запроса
не нужный в данном случае DISTINCT и ORDER by

саму сортировку выполнить средствами php по полю timestamp после того как составлен результирующий массив, или в момент его составления.

SELECT u.uid, u.name, s.timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.timestamp >= %d AND s.uid > 0

ВложениеРазмер
Иконка изображения 7may.jpg104.3 КБ

Комментарии

Аватар пользователя andypost@drupal.org andypost@drupal.org 10 января 2010 в 3:33

А как иначе вывести, что под одним логином несколько человек на сайте?

PS: кстати идея хорошая - можно сделать настройку для блока - выводить или нет сессии под одним логином.

Ромка, если человек разлогинился то никак (с такой оптимизацией) Smile

Но погонять explain и достроить какой-нить индекс можно... я что-то когда-то ваял по этому поводу...

Аватар пользователя Demimurych Demimurych 10 января 2010 в 13:04

"Ромка" wrote:
А что если пользователь разлогинился? Второй запрос в этом случае будет выдавать некорректное значение.

"<a href="mailto:andypost@drupal.org">andypost@drupal.org</a>" wrote:
Ромка, если человек разлогинился то никак (с такой оптимизацией) :)

добавить второе граничное условие. Больше такого но меньше такого - считать что пользователь онлайн. (например 1 минута плюс минус.)
Это конечно не вполне точно, но допустимо, в силу того что далеко не все разлогиниваются а следовательно и оригинальный запрос выдает таких пользователей как онлайн.

"<a href="mailto:andypost@drupal.org">andypost@drupal.org</a>" wrote:
А как иначе вывести, что под одним логином несколько человек на сайте?

Угу. В моем случае никак.

Но даже если брать это во внимание то запрос написан через жопу.
Правильно было бы делать тогда так:
SELECT u.uid, u.name, s.timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.timestamp >= %d AND s.uid > 0

Убран ненужный в данном случае DISTINCT и ORDER использование которых и приводит к появлению файлсорта с временной таблицей.

Сортировку потом сделать средствами php.

UPD: в догонку.
Только сейчас обратил внимание что оригинальный запрос так же содержит и ошибку, в случае если два человека под одним логином присутствуют на сайте, и в таблице session они окажутся с одинаковым timestamp их посчитают за одного.