Оптимизируйте запрос / подзапрос для лучшей производительности (медленно)

0

У меня есть запрос с подзапросом, который занимает около 15 секунд. Даже если я не присоединяюсь к двум столам, это довольно медленно, так что это не соединения, которые ели время. таблица jos_payplans_subscription имеет 8200 строк и размер 3,3 МБ.

У некоторых пользователей есть как активное, так и истекшее членство. Я хочу найти всех пользователей, как они неактивны (статус = 1603) и не имеет другого активного членства.

Запрос работает - просто медленно.

Вопрос: Могу ли я сделать лучше?

Спасибо

SELECT c.id, c.name, c.username, c.email, a.plan_id, d.title, a.subscription_date, a.expiration_date
FROM jos_payplans_subscription a
    LEFT JOIN jos_users c ON c.id = a.user_id
    LEFT JOIN jos_payplans_plan d ON d.plan_id = a.plan_id
WHERE a.status = 1603 
AND a.user_id NOT IN (SELECT b.user_id FROM jos_payplans_subscription b WHERE b.status = 1601)

И вот таблица создания для трех таблиц

CREATE TABLE 'jos_payplans_subscription' (
  'subscription_id' int(11) NOT NULL AUTO_INCREMENT,
  'order_id' int(11) NOT NULL,
  'user_id' int(11) NOT NULL,
  'plan_id' int(11) NOT NULL,
  'status' int(11) NOT NULL DEFAULT '0',
  'total' decimal(15,5) DEFAULT '0.00000',
  'subscription_date' datetime DEFAULT '0000-00-00 00:00:00',
  'expiration_date' datetime DEFAULT '0000-00-00 00:00:00',
  'cancel_date' datetime DEFAULT '0000-00-00 00:00:00',
  'checked_out' int(11) DEFAULT '0',
  'checked_out_time' datetime DEFAULT '0000-00-00 00:00:00',
  'modified_date' datetime DEFAULT '0000-00-00 00:00:00',
  'params' text NOT NULL,
  PRIMARY KEY ('subscription_id') USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=15103 DEFAULT CHARSET=utf8

CREATE TABLE 'jos_payplans_plan' (
  'plan_id' int(11) NOT NULL AUTO_INCREMENT,
  'title' varchar(255) NOT NULL,
  'published' tinyint(1) DEFAULT '1',
  'visible' tinyint(1) DEFAULT '1',
  'ordering' int(11) DEFAULT '0',
  'checked_out' int(11) DEFAULT '0',
  'checked_out_time' datetime DEFAULT '0000-00-00 00:00:00',
  'modified_date' datetime DEFAULT '0000-00-00 00:00:00',
  'description' text,
  'details' text,
  'params' text,
  PRIMARY KEY ('plan_id') USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=28 DEFAULT CHARSET=utf8

CREATE TABLE 'jos_users' (
  'id' int(11) NOT NULL AUTO_INCREMENT,
  'name' varchar(400) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  'username' varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  'email' varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  'password' varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  'block' tinyint(4) NOT NULL DEFAULT '0',
  'sendEmail' tinyint(4) DEFAULT '0',
  'registerDate' datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  'lastvisitDate' datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  'activation' varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  'params' mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  'lastResetTime' datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Date of last password reset',
  'resetCount' int(11) NOT NULL DEFAULT '0' COMMENT 'Count of password resets since lastResetTime',
  'otpKey' varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Two factor authentication encrypted keys',
  'otep' varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'One time emergency passwords',
  'requireReset' tinyint(4) NOT NULL DEFAULT '0' COMMENT 'Require user to reset password on next login',
  PRIMARY KEY ('id') USING BTREE,
  KEY 'idx_block' ('block') USING BTREE,
  KEY 'username' ('username') USING BTREE,
  KEY 'email' ('email') USING BTREE,
  KEY 'idx_name' ('name'(100)) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=23158 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  • 1
    Вы добавили индексы для полей, используемых для поиска? также вы на локальном или на веб-сервере, чтобы сделать тест?
  • 1
    Задавая вопросы об оптимизации запросов, вы должны запустить SHOW CREATE TABLE <tablename> таблицы SHOW CREATE TABLE <tablename> для каждой таблицы в своем запросе и включить определение в свой вопрос. В противном случае мы должны угадать ваши типы данных и индексы. Также запустите EXPLAIN <query> для запроса SQL, о котором вы спрашиваете.
Показать ещё 4 комментария
Теги:
query-optimization

4 ответа

1
Лучший ответ

Не забудьте добавить индексы в поля поиска, если их нет в ваших таблицах.

1

Я думаю, что следующий запрос должен работать на вас. Он использует LEFT JOIN, а не NOT IN, и в целом это будет работать значительно быстрее.

SELECT c.id, c.name, c.username, c.email, a.plan_id, d.title, a.subscription_date, a.expiration_date
FROM jos_payplans_subscription a
LEFT JOIN jos_users c 
ON c.id = a.user_id
LEFT JOIN jos_payplans_plan d 
ON d.plan_id = a.plan_id
LEFT JOIN jos_payplans_subscription b 
ON b.status = 1601
AND b.user_id = a.user_id
WHERE a.status = 1603
AND b.plan_id IS NULL

Это предполагает, что jos_payplans_subscription.plan_id не может быть NULL. Если это возможно, выберите любой другой столбец NOT NULL, который будет использоваться для части запроса b.plan_id IS NULL.

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

0
  • Измените NOT IN ( SELECT id... ) на NOT EXISTS ( SELECT *... AND b.user_id = a.user_id ). Причина: нет необходимости собирать "все" идентификаторы, есть только необходимость увидеть, существуют ли какие-либо.
  • Переключитесь на InnoDB; существует множество оптимизаций, доступных только в InnoDB.
  • Соберите идентификаторы, которые вы желаете, до JOINing в c и d. То есть SELECT... FROM ( SELECT... ) LEFT JOIN c... LEFT JOIN d...
0

Поскольку мы ищем всех пользователей, у которых есть только членство 1603, мы могли бы попробовать предложение WITH с помощью EXCEPT.

Редактирование: Хорошо, нет ИСКЛЮЧЕНИЯ в MySql, так что запрос не очень помогает.

WITH 
 users_1601 as (
  SELECT user_id FROM jos_payplans_subscription WHERE status = 1601
 ),
 subs_1603_only as (
  SELECT a.user_id, a.plan_id, a.subscription_date, a.expiration_date
  FROM jos_payplans_subscription a 
  LEFT JOIN users_1601 b ON a.user_id = b.user_id
  WHERE a.status = 1603
  AND b.user_id is NULL
 )
SELECT 
   c.id, c.name, c.username, c.email, 
   subs.plan_id, subs.subscription_date, subs.expiration_date,
   d.title
FROM subs_1603_only subs
LEFT JOIN jos_users c ON c.id = subs.user_id
LEFT JOIN jos_payplans_plan d ON d.plan_id = subs.plan_id

На самом деле, я немного пошатнулся. Если предположить, что другие методы, такие как правильная индексация, выполняются, я думаю, что цель этого запроса состоит в том, чтобы убедиться, что подзапрос (user_ids для статуса 1601) не выполняется для каждой строки основного запроса. Иногда вытягивание этого в предложение WITH помогает.

  • 1
    В MySQL нет оператора EXCEPT.
  • 0
    Да, вы правы в этом. Google провалился там с моей стороны.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню