具有 1 个大列的 77k 行表的缓慢求和

问题描述 投票:0回答:1

考虑以下表结构(77k 行和 237MB 转储大小,cart_data_json 列目前可以有 0.01k 到 50k 个字符)

CREATE TABLE `commerce_sales_test` (
  `commerce_sales_id` varchar(32) NOT NULL,
  `commerce_sales_date` bigint unsigned NOT NULL DEFAULT '0',  
  `total` bigint NOT NULL DEFAULT '0',
  **`cart_data_json` mediumtext,**
  PRIMARY KEY (`commerce_sales_id`),
  KEY `fast_sdate` (`commerce_sales_date` DESC)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;

运行 COUNT(*) 非常快。
运行 SUM(total) 的执行速度确实很慢(慢 1-5 秒,具体取决于 WHERE 语句)。
使用 SQL_CALC_FOUND_ROWS 运行 Select 语句也非常慢(无论限制如何)。

无论 WHERE 语句如何,以下查询都会在 30 毫秒内运行(我可以完全删除 where 并且查询仍然运行得非常快)

    SELECT  COUNT(*)
        FROM  commerce_sales_test AS cs
        WHERE  cs.commerce_sales_date >= 1673222400
          AND  cs.commerce_sales_date <= 1699844800 ;
+----------+
| COUNT(*) |
+----------+
|    35884 |
+----------+
1 row in set (0.026 sec)
    SELECT  cs.commerce_sales_id, cs.cart_data_json, cs.total
        FROM  commerce_sales_test AS cs
        WHERE  cs.commerce_sales_date >= 1673222400
          AND  cs.commerce_sales_date <= 1699844800
        LIMIT  0 , 15;

15 rows in set (0.003 sec)

以下查询将在 1-5 秒内运行,具体取决于 where 语句找到的行

    SELECT  COUNT(*), sum(cs.total)
        FROM  commerce_sales_test AS cs
        WHERE  cs.commerce_sales_date >= 1673222400
          AND  cs.commerce_sales_date <= 1699844800 ;
+----------+---------------+
| COUNT(*) | sum(cs.total) |
+----------+---------------+
|    35884 |    2631697732 |
+----------+---------------+
1 row in set (1.010 sec)
SELECT SQL_CALC_FOUND_ROWS cs.commerce_sales_id, cs.cart_data_json, cs.total FROM commerce_sales_test AS cs
WHERE  cs.commerce_sales_date >= 1673222400  AND cs.commerce_sales_date <= 1699844800   
LIMIT 0 , 15;

15 rows in set (1.879 sec)

我确实尝试将 cart_data_json 更改为 varchar(64000) ,但同样的问题仍然存在(对于我的情况,我可能需要超过 65k 的文本)

    SELECT  SQL_CALC_FOUND_ROWS cs.commerce_sales_id, cs.total
        FROM  commerce_sales_test AS cs
        WHERE  cs.commerce_sales_date >= 1673222400
          AND  cs.commerce_sales_date <= 1799844800
        LIMIT  0 , 15;
+--------------------------+---------+
| commerce_sales_id        | total   |
+--------------------------+---------+
| 240109141243/5T47/4S5    |  183000 |
| 240107212543/5T28/9S386  |  224800 |
| 240107212533/5T29/13S384 |   52900 |
| 240107212405/5T29/13S384 | 1288000 |
| 240107212225/5T29/13S384 |   31300 |
| 240107212130/5T29/13S384 |  122500 |
| 240107212107/5T16/11S382 |   10700 |
| 240107211912/5T29/13S384 |  238700 |
| 240107211817/5T28/9S386  |   27000 |
| 240107211805/5T32/12S383 |   39700 |
| 240107211522/5T32/12S383 |   18900 |
| 240107211354/5T32/12S383 |   34300 |
| 240107211342/5T29/13S384 |  100100 |
| 240107211121/5T29/13S384 |  107300 |
| 240107210954/5T28/9S386  |    5000 |
+--------------------------+---------+
15 rows in set, 1 warning (1.556 sec)

我确实尝试在 cart_data_json MEDIUMTEXT 列上最多只放置 100 个字符,这极大地增加了所有查询。

目前我真正需要的是能够快速执行SUM,我该怎么办? 对于 SQL_CALC_FOUND_ROWS,我可以简单地运行单独的 count(*) 查询来检索总行数。

我猜测解决方案可能是增加一些内存设置?

非常感谢您的帮助。

sql mysql performance database-design
1个回答
0
投票

这里有一些关于 InnoDB INDEXing 的课程:

  • SELECT COUNT(*) FROM ...
    使用最小的
    INDEX
    (日期中的那个)。因此,从磁盘中获取的内容非常少(或者在 buffer_pool 中找到缓存)。
  • 添加日期范围测试使其速度更快,因为它不需要扫描整个索引。
  • 在您的情况下,
  • SELECT ... big_column ...
    需要为大(
    MEDIUMTEXT
    中的json)进行额外的磁盘获取。这会增加很多时间。
  • 更改为
    VARCHAR(64000)
    没有影响,因为大型列的“非记录”存储仍然适用。
  • SQL_CALC_FOUND_ROWS
    根据
    LIMIT
    停止传送行,但随后继续获取(并丢弃)其余行。
  • SQL_CALC_FOUND_ROWS
    不应再使用——MySQL 8 已将其从语言中删除。所以,你的两个查询替代方案很好。此外(见上文),简单地执行
    COUNT(*)
    而不是列出列(包括 json)要快得多。
  • 没有
    LIMIT
    和没有
    SQL_CALC_FOUND_ROWS
    最多有 15 个非记录查找。
  • 总计(没有
    sum(cs.total)
    LIMIT
    )花费了中等时间,因为它必须在
    INDEX
    BTree 和数据 BTree 之间跳转 77K 次。通过仅在日期上提供当前索引的“覆盖”
    INDEX(commerce_sales_date, total)
    而不是,可以大大加快速度。 “覆盖”消除了“来回弹跳”。
  • “最多 100 个字符”消除了“不记录”查找。
  • 只要给定行占用的空间超过 8KB,“非记录”存储就会发挥作用。
  • innodb_buffer_pool_size
    应设置为可用 RAM 的 70% 左右。如果当前小于 500M,那么您可能会受益于增加设置(并在必要时增加 RAM)。 (500M 是根据您的 237MB 粗略估计的。)
© www.soinside.com 2019 - 2024. All rights reserved.