【问题标题】:Mysql group by query optimization with 400000+ recordsMysql group by 查询优化 400000+ 条记录
【发布时间】:2015-11-10 22:26:38
【问题描述】:

我有两张桌子,即 t_productspecificprice 里面有折扣 和t_productcategory 映射productidscategoryids

t_productspecificprice 可以为一个productid 添加多个折扣,但最近添加的折扣只是相关的。

t_productspecificprice 有大约 450000 条记录,并且 t_productcategory 有 ~350000+ 条记录。

我需要针对特定​​categoryid 的每个productid 的最新折扣。

下面的查询不起作用,phpmyadmin 中的错误 504。

查询:

select 
    a_categoryid as 'Category Id',
    t_productcategory.a_productid as 'Product Ids', 
    t_productspecificprice.a_reduction, 
    t_productspecificprice.a_reductiontype, 
    t_productspecificprice.a_to  

from t_productcategory

left join t_productspecificprice on t_productspecificprice.a_productid = t_productcategory.a_productid

left join 

(SELECT max(a_productspecificpriceid) as a_productspecificpriceid FROM t_productspecificprice 
    GROUP by a_productid
    ) 
as discounts on discounts.a_productspecificpriceid = t_productspecificprice.a_productspecificpriceid

where a_categoryid = 4

架构:

'

解释信息:

有人可以优化吗?

【问题讨论】:

  • 我建议显示表格架构,而不是在文本中多说。为了更好地理解。
  • 您的架构不包含有关索引的信息
  • 您遇到了 504 超时,可能是因为您的查询耗时过长。问:您在超时发生之前等待了多长时间?问:你们有什么指标? Q:你有没有做过“演出计划”?请阅读这些文章:dev.mysql.com/doc/refman/5.0/en/using-explain.htmldev.mysql.com/doc/refman/5.5/en/optimization-indexes.html。请使用 1) 索引信息和 2) 解释信息更新您的帖子。
  • 添加索引信息
  • discounts 左连接有什么意义?您没有从表格中选择任何内容,它是 left join,因此您也没有将其用作过滤器

标签: php mysql sql database optimization


【解决方案1】:

这个很有挑战性的问题。 我以前也有过这种情况,数据只有 20 万行。 我的系统通过使用两个表连接的简单概括而停止。 条件和你的差不多,只是表和数据不同。

如果你在查询之前使用explain 命令,mysql 引擎会提供你查询的执行计划。在那里你会发现 mysql 引擎必须分析的数据行数量是惊人的。不是简单的 400k + 350k。 试试下面的命令。只需在上一个查询之前添加说明

explain your_query;

explain extended your_query;

然后,尝试在执行查询期间监控 mysql 进程使用的磁盘 i/o、cpu 和内存。 然后你会发现你的 sql 是什么瓶颈。 例如,SATA 驱动器的常见性能为 20-40MB/s。 试着看看你的系统能做什么。

这是一个现在称为大数据分析的领域。 要正确分析这么大的join的结果,恐怕没有简单的解决办法。

这种大数据处理的主要问题是,mysql 引擎在缓存查询中使用的所有键时内存不足。 因此,当这种情况发生时,mysql 正在将分配的内存进出交换内存到硬盘。因此,需要添加更多处理。

解决方案将涉及重组您的表或修改硬件或添加一些辅助表。

  1. 使用辅助重述表。通过大数据行处理需要大量时间。您可能希望将查询分解为几个临时表并用组的结果填充它们。然后使用连接表的最终查询。 例如,您可以使用 tmp_recap_discount 来填充最大折扣的结果。

    insert into tmp_recap_discount 
    SELECT 
      a_productid, 
      max(a_productspecificpriceid) as a_productspecificpriceid 
    FROM    
      t_productspecificprice 
    GROUP by a_productid
    

    使用调度程序/作业来运行此查询,因为此分组将花费大量的过程和时间,然后针对此表运行一个简单的联接。如果查询还针对 400k 数据运行,建议创建一个临时表来保存结果。所以排队一个 sql 作业列表来填充临时回顾表。创建一个互斥锁或标志来标记作业是否已完成,因此 php 应用程序只需查看最终表格。没有简单的方法可以最大化针对大数据表的执行时间。即使是带有 where 子句的简单选择也会花费大量时间。因此,建议直接使用本机/桌面应用程序或使用 mysql 命令运行慢查询。不建议使用 php 执行这么慢的查询,即使您将 php 执行时间最大化到几天。可能会发生令人讨厌的事情。

  2. 安装 mariadb。 它是 mysql 的替代品。只需卸载mysql,但保留数据文件夹。然后在 mysql 安装上安装 mariadb。如果您想安全起见,请转储数据库,然后在干净的 mariadb 安装中恢复它。在我的情况下,性能不同是非常重要的。执行时间提高了 300% 以上。无需更改查询。性能提升非常显着,因为我已将我的所有系统数据库从 mysql 升级到 mariadb。 但要小心强硬,因为一些程序员经常使用讨厌的子查询,mariadb 处理子查询的方式与 mysql 略有不同。 所以所有使用 mysql 的应用程序的输出都必须经过彻底的测试。

  3. 玩弄你的硬件。优化设置。建议您先升级到 mariaDB,然后再使用硬件和设置,因为改进在那里。

    一个。优化mysql设置。尝试在您的 my.ini 或 my.cnf 中找到这些设置。这些是基本的优化设置。

    #default will be 128M, but you can increase safely around a quarter of system memory. 
    #If you have 8Gb, then it is safe to assume 2048M for innodb buffer.
    #The setting can be increased, just make sure, the system memory     have the amount free memory requested. 
    #If not, it will be using memory swapping again, and the performance will bottleneck.
    innodb_buffer_pool_size = 2G
    
    #it will force the mysql engine to save your table(s) into different file(s) instead using just one giant file to store. 
    #But if the previous setting is set to 0, you have to use a fresh mysql / mariadb install and restore the database for this setting to take effect.
    innodb_file_pertable = 1
    

    b.最大化磁盘 io。要最大化磁盘 io,只需使用更快的驱动器配置。它可能会升级到 15k RPM SAS、SSD 驱动器或 SATA 驱动器、SAS 驱动器或 SSD 驱动器的 RAID 0

    c。使用表分区。但这需要深入分析以最大限度地提高性能。 https://dev.mysql.com/doc/refman/5.1/en/partitioning.html

【讨论】:

    【解决方案2】:

    您的查询使用以下外连接:

    1. 外部加入所有产品的具体价格。
    2. 外部加入所有最新产品的具体价格。

    换句话说:

    1. 如果有特定产品的价格,请全部加入。 (否则加入空记录。)
    2. 如果产品特定价格恰好是最新的产品特定价格,请加入其 ID。 (否则加入 NULL ID。)

    因此,无论是否是最新价格,您都会保留所有记录。 (嗯,这毕竟是外连接应该做的事情。)

    例如,您可以重写您的查询,以便左外部连接最新产品特定价格:

    select 
      a_categoryid as 'Category Id',
      t_productcategory.a_productid as 'Product Ids', 
      t_productspecificprice.a_reduction, 
      t_productspecificprice.a_reductiontype, 
      t_productspecificprice.a_to  
    from t_productcategory
    left join t_productspecificprice 
      on t_productspecificprice.a_productid = t_productcategory.a_productid
      and a_productspecificpriceid in
      (
        select max(a_productspecificpriceid) 
        from t_productspecificprice 
        group by a_productid
      );
    

    NOT EXISTS 的替代方案也值得一试:

    select 
      a_categoryid as 'Category Id',
      t_productcategory.a_productid as 'Product Ids', 
      t_productspecificprice.a_reduction, 
      t_productspecificprice.a_reductiontype, 
      t_productspecificprice.a_to  
    from t_productcategory
    left join t_productspecificprice 
      on t_productspecificprice.a_productid = t_productcategory.a_productid
      and not exists
      (
        select *
        from t_productspecificprice newer
        where newer.a_productid = t_productspecificprice.a_productid
        and newer.a_productspecificpriceid > t_productspecificprice.a_productspecificpriceid
      );
    

    【讨论】:

    • 第一个查询同样的问题...我还在等待
    • @ShekharJoshi 您应该通过不使用 phpmyadmin 而是使用 mysql cli 来解决该特定问题。
    • 嗯,收集所有最新价格记录可能需要相当长的时间。第一个查询首先获取所有最新价格并对其进行处理,而第二个查询尝试动态查找最新价格。我不知道哪个更有希望。两者都可能运行很长时间。如果评估时间太长,您可能需要考虑保留冗余数据,即价格也在 t_product 中,并在 t_productspecificprice 上使用插入触发器填充它。因此,只要您对价格历史不感兴趣,您就不必访问 t_productspecificprice。
    【解决方案3】:

    我相信你应该试试这个

    SELECT 
        pc.a_categoryid AS 'Category Id',
        pc.a_productid AS 'Product Ids', 
        psp.a_reduction, 
        psp.a_reductiontype, 
        psp.a_to,
        discounts.max_price_id  
    FROM t_productcategory AS pc
    LEFT JOIN t_productspecificprice AS psp 
        ON (psp.a_productid = pc.a_productid)
    LEFT JOIN (
            SELECT a_productid, MAX(a_productspecificpriceid) AS max_price_id 
            FROM t_productspecificprice 
            GROUP BY a_productid
        ) AS discounts 
        ON discounts.max_price_id = psp.a_productspecificpriceid
    WHERE pc.a_categoryid = 4
    

    并在t_productspecificprice 表中的(a_productid, a_productspecificpriceid) 上添加一个复合键

    【讨论】:

    • 虽然显示结果需要很长时间,但它表示查询在 0.8490 秒内执行。我也没有看到没有映射折扣的产品 ID,如果没有映射折扣,我需要相应的数据为 null。
    • 答案已更新。显示 - 当然是另一个“问题”。看看@AD7six 怎么说
    【解决方案4】:

    我的同事建议了这个查询,它工作正常:

    SELECT 
      s1.a_categoryid,
      p1.a_productspecificpriceid, 
      s1.a_productid,
      p1.a_reduction,
      p1.a_from,
      p1.a_to 
    FROM t_productcategory s1 
    
    LEFT JOIN t_productspecificprice p1 ON (s1.a_productid = p1.a_productid) 
    LEFT JOIN t_productspecificprice p2 ON (p1.a_productid = p2.a_productid AND p1.a_productspecificpriceid < p2.a_productspecificpriceid) 
    
    WHERE p2.a_productid IS NULL AND s1.a_categoryid = 4
    

    【讨论】:

    • 好的。这是在没有 NOT EXISTS 子句的情况下执行 NOT EXISTS 的技巧。只有在不能很好地处理 NOT EXISTS 的弱 DBMS 中才需要。有人告诉我,MySQL 不再需要这种方法。因此,要么我被错误地告知,要么您使用的是旧版本的 MySQL。无论如何,我很高兴你找到了解决问题的方法。
    • 我们的服务器版本是 5.5.42-log - MySQL Community Server (GPL)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-16
    • 2019-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 2012-06-12
    相关资源
    最近更新 更多