MySQL поиск пользователей и их ролей - PullRequest
1 голос
/ 17 марта 2010

Я переписываю SQL, который позволяет пользователю искать любого другого пользователя на нашем сайте, а также показывает его роли.

В качестве примера, ролями могут быть «Автор», «Редактор», «Издатель».

Каждая роль связывает пользователя с публикацией.

Пользователи могут выполнять несколько ролей в нескольких публикациях.

Пример настройки таблицы:

"users" : user_id, firstname, lastname
"publications" : publication_id, name  
"link_writers" : user_id, publication_id  
"link_editors" : user_id, publication_id  

Текущий псевдо-SQL:

SELECT * FROM (
  (SELECT user_id FROM users WHERE firstname LIKE '%Jenkz%') 
  UNION 
  (SELECT user_id FROM users WHERE lastname LIKE '%Jenkz%')
) AS dt
JOIN (ROLES STATEMENT) AS roles ON roles.user_id = dt.user_id

На данный момент мои роли:

SELECT  dt2.user_id, dt2.publication_id, dt.role FROM (
  (SELECT 'writer' AS role, link_writers.user_id, link_writers.publication_id
  FROM link_writers)
  UNION
  (SELECT 'editor' AS role, link_editors.user_id, link_editors.publication_id
  FROM link_editors)
) AS dt2

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

В качестве примера "издатели" могут быть связаны между двумя таблицами

"link_publishers": user_id, publisher_group_id
"link_publisher_groups": publisher_group_id, publication_id

Таким образом, в этом случае запрос, формирующий часть моего UNION, будет:

SELECT 'publisher' AS role, link_publishers.user_id, link_publisher_groups.publication_id
FROM link_publishers
JOIN link_publisher_groups ON lpg.group_id = lp.group_id

Я довольно уверен, что мои настройки таблицы хороши (меня предупредили о системе «одна таблица для всех» при исследовании макета). Моя проблема в том, что в таблице пользователей теперь 100 000 строк и до 70 000 строк в каждой из таблиц ссылок.

Первоначальный поиск в таблице пользователей выполняется быстро, но объединение действительно замедляет работу.

Как я могу присоединиться только к соответствующим ролям?

-------------------------- РЕДАКТИРОВАТЬ -------------------- -------------- explain

Объясните выше (откройте в новом окне, чтобы увидеть полное разрешение).

Нижний бит красного цвета - это "WHERE firstname LIKE"% Jenkz% "", в третьей строке выполняется поиск WHERE CONCAT (имя, '', фамилия) LIKE "% Jenkz%". Отсюда большое количество строк, но я думаю, что это неизбежно, если только нет способа поместить индекс в объединенные поля?

Зеленый бит вверху показывает общее количество строк, отсканированных из ЗАЯВЛЕНИЯ О РОЛЯХ.

Затем вы можете увидеть каждое отдельное предложение UNION (# 6 - # 12), которое показывает большое количество строк. Некоторые индексы нормальные, некоторые уникальные.

Кажется, что MySQL не оптимизирует использование dt.user_id для сравнения внутренних операторов UNION. Есть ли способ заставить это поведение?

Обратите внимание, что мои настоящие настройки - это не публикации и не писатели, а "веб-мастера", "игроки", "команды" и т. Д.

Ответы [ 3 ]

0 голосов
/ 18 марта 2010

Проверив ответ OMG Ponies на SO - Использование коррелированного подзапроса , я пришел к следующему:

SELECT * FROM (
  (SELECT user_id FROM users WHERE firstname LIKE '%Jenkz%') 
  UNION 
  (SELECT user_id FROM users WHERE lastname LIKE '%Jenkz%')
) AS dt
JOIN ( SELECT 'writer' AS role, link_writers.user_id, link_writers.publication_id
       FROM link_writers
       UNION
       SELECT 'editor' AS role, link_editors.user_id, link_editors.publication_id
       FROM link_editors
       UNION
       SELECT 'publisher' AS role, lp.user_id, lpg.publication_id
       FROM link_publishers lp
       JOIN link_publisher_groups lpg ON lpg.publisher_group_id = lp.publisher_group_id
     ) roles on roles.user_id = dt.user_id

Объяснение выглядит разумно на моем крошечном наборе данных. Как это выглядит на реальной вещи?

0 голосов
/ 20 марта 2010

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

Для этого создайте новую таблицу «Роль»:

create table role (
     user_id int not null,
     role enum ('writer', 'editor', 'publisher' ) not null,
     primary key (user_id, role )
);

Это будет обновляться всякий раз, когда в одну из ваших таблиц ссылок будет добавлена ​​новая строка, содержащая идентификатор пользователя:

insert ignore into role values( $user_id, $role );

Через некоторое время, вероятно, запись роли уже будет существовать, отсюда и модификатор ignore.

Таблица может быть загружена из существующих таблиц:

insert ignore into role select distinct user_id, 'writer' from link_writers;
insert ignore into role select distinct user_id, 'editor' from link_editors;
insert ignore into role select distinct user_id, 'publisher' from link_publishers;

Ваш поисковый запрос становится набором простых JOINS, которые MySQL не должен иметь проблем с оптимизацией:

SELECT 
   r.user_id, 
   r.role,
   case r.role 
        when 'writer' then w.publication_id
        when 'editor' then e.publication_id
        when 'publisher' then pg.publication_id
        end as publication_id
FROM (
  (SELECT user_id FROM users WHERE firstname LIKE '%Jenkz%') 
  UNION 
  (SELECT user_id FROM users WHERE lastname LIKE '%Jenkz%')
) AS dt
JOIN role r on r.user_id = dt.user_id
LEFT JOIN link_writers w on r.user_id = w.user_id and r.role = 'writer'
LEFT JOIN link_editors e on r.user_id = e.user_id and r.role = 'editor'
LEFT JOIN link_publishers p on r.user_id = p.user_id and r.role = 'publisher'
LEFT JOIN link_publisher_groups pg on p.publisher_group_id = pg.publisher_group_id;

Это даст очень "широкий" ответ.

0 голосов
/ 17 марта 2010

Моя первоначальная идея состояла в том, чтобы создать временную таблицу для хранения (и индексации) идентификатора пользователя, соответствующего имени, и использовать его для объединения с каждой таблицей ссылок. К сожалению, в MySQL временная таблица может быть объединена только с ONCE в запросе.

Удачный обходной путь - создать постоянную таблицу, добавив connection_id к первичному ключу, чтобы отдельные сессии не путались.

create table tt ( connection_id int not null,
                  user_id int not null, 
                  firstname varchar(10) not null, 
                  lastname varchar(10) not null,
                  primary key( connection_id, user_id ) );

Следующая последовательность повторяется каждый раз, когда вам нужен ответ:

delete from tt where connection_id = connection_id();

insert into tt 
  SELECT connection_id(), user_id, firstname, lastname FROM users 
  WHERE firstname LIKE '%Jenkz%' 
  UNION 
  SELECT connection_id(), user_id, firstname, lastname FROM users 
  WHERE lastname LIKE '%Jenkz%';

Затем ваш существующий UNION расширяется, так что извлекается только соответствующий user_id:

SELECT 'writer' AS role, link_writers.user_id, link_writers.publication_id
FROM link_writers
JOIN tt ON tt.connection_id = connection_id() and tt.user_id = link_writers.user_id

UNION

SELECT 'editor' AS role, link_editors.user_id, link_editors.publication_id
FROM link_editors
JOIN tt ON tt.connection_id = connection_id() and tt.user_id = link_editors.user_id

UNION

SELECT 'publisher' AS role, link_publishers.user_id, link_publisher_groups.publication_id
FROM link_publishers
JOIN link_publisher_groups 
   ON link_publisher_groups.publisher_group_id = link_publishers.publisher_group_id
JOIN tt ON tt.connection_id = connection_id() and tt.user_id = link_publishers.user_id

Возможно, это будет улучшение, потому что не все строки всех таблиц ссылок втягиваются в объединение.

Объяснение EXPLAIN несколько своеобразно тем, что используется только 4 байта индекса на tt - где я бы ожидал все 8 байтов. Возможно, это потому, что у меня так мало данных в тт.

*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: tt
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: PRIMARY
        table: link_writers
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.tt.user_id
         rows: 1
        Extra: Using index
*************************** 3. row ***************************
           id: 2
  select_type: UNION
        table: tt
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: Using index
*************************** 4. row ***************************
           id: 2
  select_type: UNION
        table: link_editors
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.tt.user_id
         rows: 1
        Extra: Using index
*************************** 5. row ***************************
           id: 3
  select_type: UNION
        table: tt
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: Using index
*************************** 6. row ***************************
           id: 3
  select_type: UNION
        table: link_publishers
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.tt.user_id
         rows: 1
        Extra: Using index
*************************** 7. row ***************************
           id: 3
  select_type: UNION
        table: link_publisher_groups
         type: ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.link_publishers.publisher_group_id
         rows: 2
        Extra: Using index
*************************** 8. row ***************************
           id: NULL
  select_type: UNION RESULT
        table: <union1,2,3>
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
        Extra:
8 rows in set (0.00 sec)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...