MySQL 分区请求缓慢 table
MySQL slow request on partitionned table
我面临着一个完全的谜。
我创建了一个 table 来存储本地气象数据。自 1979 年以来,我每小时有一个值,每 0.25 个纬度和经度。
这使我在数据库中拥有数十亿行。
根据多个建议,我对 table 进行了分区。
我选择按年划分。这是它的样子:
CREATE TABLE `MyTable` (
`latitude_100` SMALLINT NOT NULL, -- Smallint is 2 bytes, where float is 4. So we take latitude * 100
`longitude_100` SMALLINT NOT NULL, -- Same logic here
`time` DATETIME NOT NULL,
`final` TINYINT UNSIGNED NOT NULL,
`value` DOUBLE NOT NULL,
PRIMARY KEY (`latitude_100` ASC, `longitude_100` ASC, `time` ASC)
)
PARTITION BY HASH(YEAR(time)) PARTITIONS 45 ; -- This will work until 2023 included
为了测试,我只注入了2015年到2021年的table数据
问题:
此 table 中的所有 SELECT 都非常长。
更糟糕的是,它们有时长得愚蠢。
例如:
SELECT time, latitude_100, longitude_100, value
FROM MyTable
WHERE latitude_100 BETWEEN 500 AND 2000
AND longitude_100 BETWEEN 11600 AND 12800 AND
YEAR(time) = 1990 ;
请记住,没有 1990 年的数据。通过查看正确的分区,MySQL 应该会立即看到它,不是吗?
MySQL 解释一下它会查看所有分区,我不明白为什么:
EXPLAIN SELECT time, latitude_100, longitude_100, value
FROM MyTable
WHERE latitude_100 BETWEEN 500 AND 2000
AND longitude_100 BETWEEN 11600 AND 12800 AND
YEAR(time) = 1990 ;
# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1, SIMPLE, MyTable, p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19,p20,p21,p22,p23,p24,p25,p26,p27,p28,p29,p30,p31,p32,p33,p34,p35,p36,p37,p38,p39,p40,p41,p42,p43,p44, range, PRIMARY, PRIMARY, 4, , 118295536, 11.11, Using where
当我做的时候
SELECT * FROM information_schema.partitions WHERE TABLE_SCHEMA='MySchema' AND TABLE_NAME = 'MyTable' AND PARTITION_NAME IS NOT NULL
我看到只有6个分区有数据,其他都是空的
我最后想到的是用不同的方式来表述 WHERE,也许可以利用索引:
SELECT time, latitude_100, longitude_100, value
FROM MyTable
WHERE latitude_100 BETWEEN 500 AND 2000
AND longitude_100 BETWEEN 11600 AND 12800 AND
time BETWEEN "1990-01-01 00:00:00" AND "1990-12-31 23:00:00" AND
YEAR(time) = 1990 ;
但这并不能加速执行。只有 EXPLAIN 有点不同(但不是在分区读取方面):
# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1, SIMPLE, MyTable, p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19,p20,p21,p22,p23,p24,p25,p26,p27,p28,p29,p30,p31,p32,p33,p34,p35,p36,p37,p38,p39,p40,p41,p42,p43,p44, range, PRIMARY, PRIMARY, 9, , 118295536, 1.23, Using where
我做错了什么?
为什么MySQL不想配合分区?
非常感谢!
[编辑]
在技术方面,数据库托管在 AWS RDS 上。它由“db.t4g.large”实例和用户 MySQL 8.0.27
提供支持
不要使用 PARTITION BY HASH
! 在使用日期范围(就像你有的!)时,HASH 将无法执行任何 p运行ing。简而言之,优化器不够智能,无法看到您的范围适合单个分区。此外,HASH
可能不必要地将两个不同的年份归为同一个分区。相反,使用 PARTITION BY RANGE
.
我知道 RANGE(TO_DAYS(time))
有效;也许 RANGE(YEAR(time))
可以工作,这取决于您使用的 MySQL 的版本;查看详情。
小时: 通过一些日期算法,您可以将 5 字节 DATETIME
缩小为 3 字节 MEDIUMINT
。 (需要对 PARTITION BY RANGE
进行适当更改。)
不够: 由于您只使用 7 年的数据进行测试,因此我的分区建议只能提供 7 倍的帮助。
DOUBLE? 你在测量什么? DOUBLE
占用 8 个字节,并为您提供大约 16 位有效数字。即使是 FLOAT
(4 个字节,7 个数字)也可能有点矫枉过正。对于温度 (°C),请考虑 DECIMAL(2)
或 TINYINT
(-128..+127) 或 DECIMAL(4,2)
;它们分别是 1,1,2 字节。极端记录:-89..+57。注意:°F 在任何 INT
或 DECIMAL
编码中都需要多一个字节。 (我猜如果温度超过 99°C,仪器太靠近火山或野火将无法传输数据。)
缩小 DOUBLE
会将数据集大小缩小约 1/3——值得付出努力。
如果您最终会得到大约 400GB 的行,数据类型大小非常重要。
所以,让我们深入挖掘...请提供
- RAM 容量
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
- 您可能 运行 的任何其他
SELECTs
,包括 WHERE
一年以外的条款。
- 你7年用了多少盘space?如果使用 MyISAM,我预计大约有 1.2TB;如果使用 InnoDB,3TB。
- 样本 Select 中的 lat/lng 范围相对较小。这是典型的吗?如果是这样,我们也许可以利用它。
ENGINE -- 因为我认为这主要是一个只读数据集,所以 MyISAM 更好的情况可能很少见。见上面的估计;乘以 6 得到 43 年的估计值。
用法 -- 您将如何处理 SELECT
这样的结果?如果那是 'only' 查询,那么有更紧凑的方式来存储数据。但是它们对于 Insert 和 Select 来说会更复杂。然而,速度的提高可能是值得的。在进一步建议之前,我需要查看各种 Select。
我面临着一个完全的谜。
我创建了一个 table 来存储本地气象数据。自 1979 年以来,我每小时有一个值,每 0.25 个纬度和经度。 这使我在数据库中拥有数十亿行。 根据多个建议,我对 table 进行了分区。 我选择按年划分。这是它的样子:
CREATE TABLE `MyTable` (
`latitude_100` SMALLINT NOT NULL, -- Smallint is 2 bytes, where float is 4. So we take latitude * 100
`longitude_100` SMALLINT NOT NULL, -- Same logic here
`time` DATETIME NOT NULL,
`final` TINYINT UNSIGNED NOT NULL,
`value` DOUBLE NOT NULL,
PRIMARY KEY (`latitude_100` ASC, `longitude_100` ASC, `time` ASC)
)
PARTITION BY HASH(YEAR(time)) PARTITIONS 45 ; -- This will work until 2023 included
为了测试,我只注入了2015年到2021年的table数据
问题: 此 table 中的所有 SELECT 都非常长。
更糟糕的是,它们有时长得愚蠢。 例如:
SELECT time, latitude_100, longitude_100, value
FROM MyTable
WHERE latitude_100 BETWEEN 500 AND 2000
AND longitude_100 BETWEEN 11600 AND 12800 AND
YEAR(time) = 1990 ;
请记住,没有 1990 年的数据。通过查看正确的分区,MySQL 应该会立即看到它,不是吗?
MySQL 解释一下它会查看所有分区,我不明白为什么:
EXPLAIN SELECT time, latitude_100, longitude_100, value
FROM MyTable
WHERE latitude_100 BETWEEN 500 AND 2000
AND longitude_100 BETWEEN 11600 AND 12800 AND
YEAR(time) = 1990 ;
# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1, SIMPLE, MyTable, p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19,p20,p21,p22,p23,p24,p25,p26,p27,p28,p29,p30,p31,p32,p33,p34,p35,p36,p37,p38,p39,p40,p41,p42,p43,p44, range, PRIMARY, PRIMARY, 4, , 118295536, 11.11, Using where
当我做的时候
SELECT * FROM information_schema.partitions WHERE TABLE_SCHEMA='MySchema' AND TABLE_NAME = 'MyTable' AND PARTITION_NAME IS NOT NULL
我看到只有6个分区有数据,其他都是空的
我最后想到的是用不同的方式来表述 WHERE,也许可以利用索引:
SELECT time, latitude_100, longitude_100, value
FROM MyTable
WHERE latitude_100 BETWEEN 500 AND 2000
AND longitude_100 BETWEEN 11600 AND 12800 AND
time BETWEEN "1990-01-01 00:00:00" AND "1990-12-31 23:00:00" AND
YEAR(time) = 1990 ;
但这并不能加速执行。只有 EXPLAIN 有点不同(但不是在分区读取方面):
# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1, SIMPLE, MyTable, p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19,p20,p21,p22,p23,p24,p25,p26,p27,p28,p29,p30,p31,p32,p33,p34,p35,p36,p37,p38,p39,p40,p41,p42,p43,p44, range, PRIMARY, PRIMARY, 9, , 118295536, 1.23, Using where
我做错了什么? 为什么MySQL不想配合分区?
非常感谢!
[编辑] 在技术方面,数据库托管在 AWS RDS 上。它由“db.t4g.large”实例和用户 MySQL 8.0.27
提供支持不要使用 PARTITION BY HASH
! 在使用日期范围(就像你有的!)时,HASH 将无法执行任何 p运行ing。简而言之,优化器不够智能,无法看到您的范围适合单个分区。此外,HASH
可能不必要地将两个不同的年份归为同一个分区。相反,使用 PARTITION BY RANGE
.
我知道 RANGE(TO_DAYS(time))
有效;也许 RANGE(YEAR(time))
可以工作,这取决于您使用的 MySQL 的版本;查看详情。
小时: 通过一些日期算法,您可以将 5 字节 DATETIME
缩小为 3 字节 MEDIUMINT
。 (需要对 PARTITION BY RANGE
进行适当更改。)
不够: 由于您只使用 7 年的数据进行测试,因此我的分区建议只能提供 7 倍的帮助。
DOUBLE? 你在测量什么? DOUBLE
占用 8 个字节,并为您提供大约 16 位有效数字。即使是 FLOAT
(4 个字节,7 个数字)也可能有点矫枉过正。对于温度 (°C),请考虑 DECIMAL(2)
或 TINYINT
(-128..+127) 或 DECIMAL(4,2)
;它们分别是 1,1,2 字节。极端记录:-89..+57。注意:°F 在任何 INT
或 DECIMAL
编码中都需要多一个字节。 (我猜如果温度超过 99°C,仪器太靠近火山或野火将无法传输数据。)
缩小 DOUBLE
会将数据集大小缩小约 1/3——值得付出努力。
如果您最终会得到大约 400GB 的行,数据类型大小非常重要。
所以,让我们深入挖掘...请提供
- RAM 容量
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
- 您可能 运行 的任何其他
SELECTs
,包括WHERE
一年以外的条款。 - 你7年用了多少盘space?如果使用 MyISAM,我预计大约有 1.2TB;如果使用 InnoDB,3TB。
- 样本 Select 中的 lat/lng 范围相对较小。这是典型的吗?如果是这样,我们也许可以利用它。
ENGINE -- 因为我认为这主要是一个只读数据集,所以 MyISAM 更好的情况可能很少见。见上面的估计;乘以 6 得到 43 年的估计值。
用法 -- 您将如何处理 SELECT
这样的结果?如果那是 'only' 查询,那么有更紧凑的方式来存储数据。但是它们对于 Insert 和 Select 来说会更复杂。然而,速度的提高可能是值得的。在进一步建议之前,我需要查看各种 Select。