【问题标题】:Optimize query / subquery for better performance (slow)优化查询/子查询以获得更好的性能(慢)
【发布时间】:2018-09-18 22:38:57
【问题描述】:

我有一个需要大约 15 秒才能完成的子查询的查询。即使我不加入 2 个表,它也很慢,所以不是连接占用了时间。 jos_payplans_subscription 表有 8200 行,大小为 3.3 MB。

某些用户同时拥有有效和过期的会员资格。我想找到所有用户如何不活跃(状态 = 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)

这是 3 个表的创建表

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

【问题讨论】:

  • 您是否在搜索使用的字段上添加了索引?你也是在本地还是在网络服务器上进行测试?
  • 在询问有关查询优化的问题时,您应该为查询中的每个表运行SHOW CREATE TABLE <tablename>,并在问题中包含定义。否则,我们必须猜测您的数据类型和索引。还为您询问的 SQL 查询运行 EXPLAIN <query>
  • @BillKarwin 我为 3 个表添加了 show create table。我看到了主表; jos_payplans_subscription 没有索引。
  • @zeflex 我正在一个只运行这个网站的实时高性能网络服务器上进行测试
  • @zeflex 我手动向表中添加了一个新索引,这真的很神奇。从 15 秒到 0.5 秒 - 太棒了 :-) 感谢您的指点 - 没想到,因为该表是作为 Joomla CMS 扩展的一部分而创建的

标签: mysql query-optimization


【解决方案1】:

由于我们正在寻找所有只有 1603 成员资格的用户,我们可以尝试使用 EXCEPT 的 WITH 子句。

编辑:好的,在 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

实际上,我只是稍微调整了一下。假设其他做法(例如正确的索引)已完成,我认为此查询的目标是确保不会对主查询中的每一行执行子查询(状态 1601 的 user_ids)。有时将其提取到 WITH 子句中会有所帮助。

【讨论】:

  • MySQL 没有 EXCEPT 运算符。
  • 是的,你说得对。谷歌在我这方面失败了。
  • 哈,现在我编辑了我的查询,我发现它使用了与威廉的查询相同的技巧。
【解决方案2】:

我认为以下查询应该适合您。它使用了 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。如果可以,请选择任何其他不为 NULL 的列用于查询的 b.plan_id IS NULL 部分。

如果查询仍然太慢,那么我们需要查看您的索引和列数据类型,因此请为您的问题涉及的所有表添加 SHOW CREATE TABLE tablename

【讨论】:

    【解决方案3】:
    • NOT IN ( SELECT id ... ) 更改为NOT EXISTS ( SELECT * ... AND b.user_id = a.user_id )。原因:不需要收集“所有”id,只需要查看是否存在。
    • 切换到 InnoDB;有许多优化只在 InnoDB 中可用。
    • JOINingcd之前收集你想要的id。即SELECT ... FROM ( SELECT ... ) LEFT JOIN c ... LEFT JOIN d ...

    【讨论】:

      【解决方案4】:

      如果您的表中没有索引,请不要忘记在搜索字段上添加索引。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-19
        • 2021-12-03
        相关资源
        最近更新 更多