【问题标题】:How to define Indices on huge table (>38.700.000 rows) using group by, where and order by clause如何使用 group by、where 和 order by 子句在大表(>38.700.000 行)上定义索引
【发布时间】:2017-09-06 17:50:31
【问题描述】:

我正在尝试加快对大型 MySQL 表(超过 ~38.700.000 行)的查询,但我不知道该怎么做。我阅读了几十篇相关的文章,甚至对 MySQL-Documentation 感到困惑。 我承认,我需要帮助!

这是我当前的表结构:

    CREATE TABLE `bz_all_part` (
        `salon` smallint(6) unsigned NOT NULL DEFAULT '0',
        `datum` int(10) unsigned NOT NULL DEFAULT '936868149',
        `datumuhrzeit` int(10) unsigned NOT NULL DEFAULT '936868149',
        `ma` mediumint(6) unsigned NOT NULL DEFAULT '0',
        `ztyp` int(10) unsigned NOT NULL DEFAULT '0',
        `zart` int(10) unsigned NOT NULL DEFAULT '0',
        `storno` tinyint(1) unsigned NOT NULL DEFAULT '0',
        `betrag` int(10) NOT NULL DEFAULT '0',
        `betragorig` int(10) NOT NULL DEFAULT '0',

       KEY `test1` (`ztyp`,`storno`,`ma`),
       KEY `test2` (`ztyp`,`storno`),
       KEY `test3` (`ma`),
       KEY `test4` (`ztyp`,`datum`),
       KEY `test5` (`datumuhrzeit`,`ztyp`,`storno`),
       KEY `test6` (`ma`,`ztyp`,`datumuhrzeit`),
       KEY `test7` (`ztyp`,`storno`,`datumuhrzeit`),
       KEY `test8` (`ztyp`,`storno`,`datumuhrzeit`,`zart`),
       KEY `test9` (`salon`,`ma`,`zart`,`ztyp`,`storno`,`datumuhrzeit`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8

    /*!50100 PARTITION BY RANGE (datumuhrzeit)
    (PARTITION s0 VALUES LESS THAN (1391210639) ENGINE = MyISAM,
     PARTITION s1 VALUES LESS THAN (1393889039) ENGINE = MyISAM,
        ...
     PARTITION s47 VALUES LESS THAN (1514762639) ENGINE = MyISAM) */;

**datum****datumuhrzeit** 字段是 Unix 时间戳。我认为它们比 datetime 快。

如您所见,我有很多分区(当我选择较小的时段时,它们会加快查询速度)和一些测试索引。他们没有多大帮助。

我读了很多关于多列索引中的列应该按照正确的顺序(因为它们在查询中使用)。我用 Index test7test9 试过这个,但我没有运气。

这是我使用的查询:

SELECT 
    *,
    ROUND(betrag / anzBelege, 2) as umsJeBeleg,
    ROUND(betragorig / anzBelege, 2) as umsJeBelegOrig
FROM
    (SELECT 
        count(distinct concat(salon, datumuhrzeit)) as anzBelege,
        SUM(zart) as anzServices,
        SUM(ROUND(betrag / 100, 2)) as betrag,
        SUM(ROUND(betragorig / 100, 2)) as betragorig
    FROM 
        bz_all_part
    WHERE
            ztyp = 0
        AND
            storno = 0
        AND
            datumuhrzeit >= unix_timestamp("2013-01-01 00:00:00")
        AND
            datumuhrzeit <= unix_timestamp("2018-07-01 00:00:00")
    GROUP BY
        salon,
        ma, 
        zart) core

当我运行此查询时,结果包含 526607 行,大约需要 206 秒。

解释输出:

id|select_type|table      |type|possible_keys    |key  |key_len|ref        |rows    |Extra
--|-----------|-----------|----|-----------------|-----|-------|-----------|--------|-----
1 |PRIMARY    |<derived2> |ALL |NULL             |NULL |NULL   |NULL       |22955178|NULL
2 |DERIVED    |bz_all_part|ref |test1,2,4,5,7,8,9|test2|5      |const,const|22955178|Using where; Using filesort

可能的键 包含索引 test7 和 test9,但它不使用它们,而是使用 test2。我不知道为什么!

非常感谢有关此主题的一些帮助。

非常感谢!

附加信息:

  • 服务器不是最快的(它只是一个测试服务器)
  • 我们预计每年增长约 15.000.000 行
  • 上述查询将是几十个查询之一(它们都完全不同,用于获取统计信息)

编辑 1:
输出:

SELECT * FROM bz_all_part PROCEDURE ANALYSE();


Field_name                  |Min_value| Max_value|Min_length|Max_length|Empties_or_zeros|Nulls|Avg_value_or_avg_length|Std        |Optimal_fieldtype 
----------------------------|---------|----------|----------|----------|----------------|-----|-----------------------|-----------|-----------------------------
db.bz_all_part.salon        |0        |60974     |1         |5         |1               |0    |13357.9473             |17056.3346 |SMALLINT(5) UNSIGNED NOT NULL
db.bz_all_part.datum        |0        |1490911200|1         |10        |1               |0    |1440703052.6328        |427695.2284|INT(10) UNSIGNED NOT NULL
db.bz_all_part.datumuhrzeit |0        |1490989761|1         |10        |1               |0    |1440755042.1115        |460817.3074|INT(10) UNSIGNED NOT NULL
db.bz_all_part.ma           |0        |60127     |1         |5         |43              |0    |1166.8595              |7840.3516  |SMALLINT(5) UNSIGNED NOT NULL
db.bz_all_part.ztyp         |0        |40181     |1         |5         |26257963        |0    |1140.0327              |4650.5316  |SMALLINT(5) UNSIGNED NOT NULL
db.bz_all_part.zart         |0        |55158     |1         |5         |1               |0    |3338.9947              |8165.2681  |SMALLINT(5) UNSIGNED NOT NULL
db.bz_all_part.storno       |0        |2         |1         |1         |38604746        |0    |0.0074                 |0.1111     |ENUM('0','1','2') NOT NULL
db.bz_all_part.betrag       |-45240   |269900    |1         |6         |2123326         |0    |1438.2915              |1916.8851  |MEDIUMINT(6) NOT NULL
db.bz_all_part.betragorig   |-45240   |1711250   |1         |7         |2546610         |0    |1491.6800              |2089.4486  |MEDIUMINT(7) NOT NULL

编辑 2:

CREATE TABLE `bz_all_part2` (
  `salon` smallint(6) unsigned NOT NULL DEFAULT '0',
  `datum` timestamp NOT NULL,
  `datumuhrzeit` timestamp NOT NULL,
  `ma` mediumint(6) unsigned NOT NULL DEFAULT '0',
  `ztyp` int(10) unsigned NOT NULL DEFAULT '0',
  `zart` int(10) unsigned NOT NULL DEFAULT '0',
  `storno` enum('0','1','2') DEFAULT NULL,
  `betrag` mediumint(7) NOT NULL DEFAULT '0',
  `betragorig` mediumint(7) NOT NULL DEFAULT '0',
  KEY `test1` (`ztyp`,`storno`,`datum`,`zart`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

新的解释输出:

id|select_type|table      |type|possible_keys    |key  |key_len|ref        |rows    |Extra
--|-----------|-----------|----|-----------------|-----|-------|-----------|--------|-----
1 |PRIMARY    |<derived2> |ALL |NULL             |NULL |NULL   |NULL       |2243987 |NULL
2 |DERIVED    |bz_all_part|ref |test1            |test1|6      |const,const|2243987 |Using index condition; Using where; Using filesort

输出:

SELECT * FROM bz_all_part PROCEDURE ANALYSE();

和上面差不多

我将表格缩小到 5.000.000 行以获得更快的结果。

还有什么想法吗?

【问题讨论】:

  • 您的索引过多。您使用依赖于 HDD 的 MyISAM。 InnoDB 能够将工作数据存储在 RAM 中,从而更快地执行 I/O 操作。 TIMESTAMP 数据类型在内部存储一个整数,但它会将值显示为DATETIME - 如果int,您应该使用它来代替。添加索引不会使查询更快,如果像您的情况一样,有很多满足条件的记录(在 WHERE 子句或 JOIN/子查询等中)。您的日期范围在 2013 年到 2018 年之间 - 可能会有很多记录满足该条件。
  • 你能把 SELECT * FROM bz_all_part PROCEDURE ANALYSE(); 的输出发给我们吗?这可能需要一些时间。键 test2、test7 和 test8 也相同。你只使用test8。另外我不确定您是否使用最好的存储引擎。 MyISAM 在写入方面表现不佳
  • @BerndBuffen:我已经编辑了我的问题。
  • @Mjh:我将根据您的建议更改我的表格。这需要一些时间。我会回复你的。
  • 您可以做一些事情。但是,这些取决于表格的使用(即使在写入时)。它从 STORAGE ENGINE 开始,遍历索引中字段的顺序,还可以优化查询。如果你喜欢你可以很高兴地给我打电话,然后我们可以讨论这些事情并只发布结果。我今天从大约 18:00 时钟有空

标签: mysql performance indexing myisam database-partitioning


【解决方案1】:
   KEY `test1` (`ztyp`,`storno`,`ma`),
   KEY `test2` (`ztyp`,`storno`),

第二个是多余的,因为它是第一个的前缀,去掉它。

切换到 InnoDB 提供PRIMARY KEY。 MyISAM 将在下一个版本中消失。

stornoTINYINT 更改为ENUM 对表大小或查询性能没有影响。但是,更改为NULL 可能会产生很小的负面影响。

48 个分区可能减慢了查询速度,而不是加快了查询速度。您已经有了“完美”索引:(ztyp,storno,datumuhrzeit)。

“526607 行 206 秒”——但是它必须扫描多少行才能执行子查询? 是昂贵的部分,我认为。事实上,你为什么不把子查询拉出来,计时;那么如果它是总数的大部分,让我们专注于它。

但是,够了。可能这个就是答案...

建立和维护一个汇总表。

也许……

PRIMARY KEY(salon, ztyp, storno, month)
SUM(zart) AS sum_zart
SUM(...)  etc

其中monthdatumuhrzeit 截断为月份(或日期或任何有意义的)。 考虑一下这样的表是否可以让您运行子查询。

More on Summary Tables.

这可能会使内部查询的运行速度提高 10 倍。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多