超大表上的 MySQL 数据库性能选择
Posted
技术标签:
【中文标题】超大表上的 MySQL 数据库性能选择【英文标题】:MySQL database performance select on ultra large table 【发布时间】:2018-09-08 07:56:39 【问题描述】:我有一个销售数据表,其中平均每天插入 1,329,415 行。我必须每天从表格中生成不同格式的报告。但是从表中查询太慢了。这是我的 SHOW CREATE TABLE 命令输出。
CREATE TABLE `query_manager_table` (
`mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`region_id` int(2) NOT NULL,
`rtslug` varchar(10) DEFAULT NULL,
`dsid` int(3) NOT NULL,
`dpid` int(3) NOT NULL,
`route_number` int(4) NOT NULL,
`route_id` int(11) NOT NULL,
`rtlid` int(11) NOT NULL,
`retailer_code` varchar(16) DEFAULT NULL,
`platform_code` varchar(16) DEFAULT NULL,
`prid` int(4) NOT NULL,
`skid` int(4) NOT NULL,
`group` int(4) NOT NULL,
`family` int(4) NOT NULL,
`volume` float DEFAULT NULL,
`value` float(7,2) DEFAULT NULL,
`date` date NOT NULL DEFAULT '0000-00-00',
`outlets` int(4) NOT NULL,
`visited` int(4) NOT NULL,
`channel` int(3) DEFAULT NULL,
`subchannel` int(3) DEFAULT NULL,
`tpg` int(4) DEFAULT NULL,
`ioq` int(10) DEFAULT NULL,
`sales_time` int(11) DEFAULT NULL,
PRIMARY KEY (`dpid`,`route_id`,`rtlid`,`prid`,`skid`,`date`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50100 PARTITION BY LIST (YEAR(date) * 100 + QUARTER(date))
(PARTITION y2017q1 VALUES IN (201701) ENGINE = InnoDB,
PARTITION y2017q2 VALUES IN (201702) ENGINE = InnoDB,
PARTITION y2017q3 VALUES IN (201703) ENGINE = InnoDB,
PARTITION y2017q4 VALUES IN (201704) ENGINE = InnoDB,
PARTITION y2018q1 VALUES IN (201801) ENGINE = InnoDB,
PARTITION y2018q2 VALUES IN (201802) ENGINE = InnoDB,
PARTITION y2018q3 VALUES IN (201803) ENGINE = InnoDB,
PARTITION y2018q4 VALUES IN (201804) ENGINE = InnoDB,
PARTITION y2019q1 VALUES IN (201901) ENGINE = InnoDB,
PARTITION y2019q2 VALUES IN (201902) ENGINE = InnoDB,
PARTITION y2019q3 VALUES IN (201903) ENGINE = InnoDB,
PARTITION y2019q4 VALUES IN (201904) ENGINE = InnoDB) */
现在我只想通过以下查询了解从 9 月 1 日到 9 月 9 日的零售商销售额 -
SELECT
query_manager_table.dpid,
query_manager_table.route_id,
query_manager_table.rtlid,
query_manager_table.prid,
SUM(query_manager_table.`volume`) AS sales,
1 AS memos
FROM
query_manager_table
WHERE
query_manager_table.date BETWEEN '2018-09-01'
AND '2018-09-08'
GROUP BY
query_manager_table.dpid,
query_manager_table.rtlid,
query_manager_table.date
但这需要大约 500-700 秒。我添加了dpid IN (1,2,.....)
和prid IN (1,2,....)
,因为这两个文件都被添加为主键。然后输出在 300 秒后出现。我做错了什么?
+----+-------------+---------------------+------+---------------+------+---------+------+-----------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+------+---------------+------+---------+------+-----------+----------------------------------------------+
| 1 | SIMPLE | query_manager_table | ALL | PRIMARY | NULL | NULL | NULL | 129065467 | Using where; Using temporary; Using filesort |
+----+-------------+---------------------+------+---------------+------+---------+------+-----------+----------------------------------------------+
当我在 where 条件下添加所有 dpid 和 prid 时,EXPAIN 看起来像
+----+-------------+---------------------+-------+---------------+---------+---------+------+--------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+-------+---------------+---------+---------+------+--------+----------------------------------------------+
| 1 | SIMPLE | query_manager_table | range | PRIMARY | PRIMARY | 4 | NULL | 128002 | Using where; Using temporary; Using filesort |
+----+-------------+---------------------+-------+---------------+---------+---------+------+--------+----------------------------------------------+
有没有办法优化表或查询? 如果我为第一个运行 EXPLAIN PARTITIONS SELECT... 然后得到 -
+----+-------------+---------------------+-------------------------------------------------------------------------------------------------+------+---------------+------+---------+------+-----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+-------------------------------------------------------------------------------------------------+------+---------------+------+---------+------+-----------+----------------------------------------------+
| 1 | SIMPLE | query_manager_table | y2017q1,y2017q2,y2017q3,y2017q4,y2018q1,y2018q2,y2018q3,y2018q4,y2019q1,y2019q2,y2019q3,y2019q4 | ALL | PRIMARY | NULL | NULL | NULL | 127129410 | Using where; Using temporary; Using filesort |
+----+-------------+---------------------+-------------------------------------------------------------------------------------------------+------+---------------+------+---------+------+-----------+----------------------------------------------+
我得到的第二个 -
+----+-------------+---------------------+-------------------------------------------------------------------------------------------------+-------+---------------+---------+---------+------+--------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+-------------------------------------------------------------------------------------------------+-------+---------------+---------+---------+------+--------+----------------------------------------------+
| 1 | SIMPLE | query_manager_table | y2017q1,y2017q2,y2017q3,y2017q4,y2018q1,y2018q2,y2018q3,y2018q4,y2019q1,y2019q2,y2019q3,y2019q4 | range | PRIMARY | PRIMARY | 4 | NULL | 153424 | Using where; Using temporary; Using filesort |
+----+-------------+---------------------+-------------------------------------------------------------------------------------------------+-------+---------------+---------+---------+------+--------+----------------------------------------------+
【问题讨论】:
我认为您对主键和索引感到困惑。您应该索引dpid
、rtlid
、date
(这三个的复合索引),而不是将它们添加到主键
@MadhurBhaiya,主键本身不是索引吗?
是的。但这并不意味着您将每个字段都定义为 PK。主键基本上是指数据库中的主要引用唯一字段。
@MadhurBhaiya,根据我的项目场景,我的 PK 是正确的。我实际搜索日期范围的最长时间。 dpid 很少使用。我应该分别添加日期和 dpid 作为键吗?
当您说“根据您的项目场景 PK 是正确的”时,您究竟是什么意思?我同意@MadhurBhaiya;您的关键结构似乎具有比唯一标识每条记录严格要求的更多字段。请问您所说的这个“项目方案”是什么?
【参考方案1】:
INDEXes
用于提高SELECTs
的效率。
PRIMARY KEY
(在 mysql 中)根据定义是唯一的 INDEX
。它应该有一组最小的列来唯一标识一行。
任何唯一索引(包括 PK)也是“唯一性约束”——这可以防止插入具有相同 if 值集的多行。
索引是“从左边”使用的。也就是说,对于INDEX(a,b)
,如果a
没有用处,它就不会到达b
。
PARTITION BY LIST
几乎没用。它很少(如果有的话)提高性能。您向我们展示了几个问题;让我们看看更多典型查询,以便我们可以帮助您处理索引和分区。
WHERE
query_manager_table.date BETWEEN '2018-09-01'
AND '2018-09-08'
请求INDEX(date)
。在复合索引中,不会到达“范围”之后的列。也就是说,在INDEX(date, x, y)
中,测试date
的范围(例如WHERE
中的8 天),不会让它使用x
或y
。另一方面,WHERE date = '2018-09-01' AND x=1
将使用更多的索引。
float(7,2)
-- 不要在FLOAT
或DOUBLE
上使用(m,n)
选项。而是切换到DECIMAL
。
INT
始终为 4 个字节。请参阅TINYINT
(1 字节)、SMALLINT
(2 字节)等。仅此一项,就可以将表大小减半。
解释一下:
PRIMARY KEY (`dpid`,`route_id`, ...
WHERE ... AND dpid IN (...) AND ...
设法将第一个(记住:'leftmost')用于伪范围 IN
,但不能在 PK 中使用其他任何东西,因为 route_id
是下一个。
这解释了为什么第二个 EXPLAIN
有一个较小的“行”。另外,请注意“key_len”中的“4”——这是dpid
中的字节数。
完成其中一些更改后,请返回,以便我们讨论如何使用汇总表加快处理速度。但是,“修改”可能会导致这种优化变得复杂。
你有多少内存? innodb_buffer_pool_size
的值是多少?
除非必须,否则不要使用 GUID;由于随机性,它们会减慢大型表上的操作。
【讨论】:
非常感谢您真正解释的答案。我的服务器 RAM 是 64GB,innodb_buffer_pool_size 是 21474836480。大多数时候我需要日期范围之间的数据。所以你有什么建议?我会使用自动增量 PK 并将现有的 PK 设为 UK 吗?还要为每列(当前 PK)制作单个索引吗?我的表大小超过 100GB,并且每天都在增加。 什么版本的 MySQL?请为这两个查询提供EXPLAIN PARTITIONS SELECT ...
。
问题已编辑。请看一看。我的 MySQL 版本:5.6.40
@MdRiadHossain - 注意所有分区是如何列出的,从而表明没有“分区修剪”,从而支持我的观点,即PARTITION BY LIST
无益。【参考方案2】:
我不会结合实际数据字段来创建主键。我将有一个字段,并使用一个自动递增的整数或者可能是一个 GUID 作为值。必须通过六个字段来识别唯一记录比通过一个需要更多时间,并且正如您所说,如果用户输入关键数据,您将面临重复字段的风险。
如果您出于业务原因将这六个字段放在一起时是唯一的,您还应该制定一个例程来确定插入的记录是否与这些字段的现有记录重复。如果您是批量插入,您需要在插入记录后执行此操作,而不是在插入时检查每条记录。您还需要索引这六个字段,以加快重复查询。
至于您的SELECT
查询,您可能希望索引WHERE
子句中的字段。在任何情况下,您都需要阅读执行计划并尝试使用不同的索引和关键结构(可能更容易对数据子集进行操作)。谷歌“mysql 执行计划”获取大量信息。
【讨论】:
以上是关于超大表上的 MySQL 数据库性能选择的主要内容,如果未能解决你的问题,请参考以下文章