У меня есть запрос с подзапросом, который занимает около 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
Не забудьте добавить индексы в поля поиска, если их нет в ваших таблицах.
Я думаю, что следующий запрос должен работать на вас. Он использует 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
для всех таблиц, связанных с вашим вопросом.
NOT IN ( SELECT id... )
на NOT EXISTS ( SELECT *... AND b.user_id = a.user_id )
. Причина: нет необходимости собирать "все" идентификаторы, есть только необходимость увидеть, существуют ли какие-либо.JOINing
в c
и d
. То есть SELECT... FROM ( SELECT... ) LEFT JOIN c... LEFT JOIN d...
Поскольку мы ищем всех пользователей, у которых есть только членство 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 помогает.
SHOW CREATE TABLE <tablename>
таблицыSHOW CREATE TABLE <tablename>
для каждой таблицы в своем запросе и включить определение в свой вопрос. В противном случае мы должны угадать ваши типы данных и индексы. Также запуститеEXPLAIN <query>
для запроса SQL, о котором вы спрашиваете.