围绕唯一数据对MySQL表进行分区

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

我有一个带有架构的Mysql表,如下所示:

CREATE TABLE `historical_pricing` (
  `date` date DEFAULT NULL,
  `company` varchar(12) DEFAULT NULL,
  `price` double(20,5) DEFAULT NULL,
  `vol` double DEFAULT NULL,
  `id` varchar(25) NOT NULL,
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `date_idx` (`date`),
  KEY `company_idx` (`company`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

已经加载了大约12GB的数据,并且在合理的性能计算机上运行查询以获取不同的代码不会在15分钟内完成。

我有几个查找的微服务:

  • 我们在SELECT distinct(company) from historical_pricing上有数据的所有公司的列表
  • 我们在每家公司SELECT * FROM historical_pricing where company = 'AAPL' ORDER BY date desc上的所有定价数据

我阅读了MySQL 5.7文档,我认为我可以通过1)按公司名称划分来加速查询,2)设置另一个日期索引。有一个8192 partition limit in MySQL 5.7,所以我考虑使用基于起始名称的分区:a%b%1%2%等。

我想弄明白:

  • 添加分区的MySQL ALTER TABLE语法是什么?我无法弄清楚这是一个列表,范围等。
  • 为company_date_idx(公司首先,然后是日期)做一个索引是否有意义,或者date_idx是否足够,因为索引将在每个分区中创建?
  • 有没有更有效的方法来优化我的查询?

编辑::

我还有另一个做公司的索引,日期为多列索引。我注意到的是,无论如何,每次查找都需要重新运行整个表。如果我应该放大我的innodb_buffer_pool_size,我运行以下查询:

SELECT engine,
  count(*) as TABLES,
  concat(round(sum(table_rows)/1000000,2),'M') rows,
  concat(round(sum(data_length)/(1024*1024*1024),2),'G') DATA,
  concat(round(sum(index_length)/(1024*1024*1024),2),'G') idx,
  concat(round(sum(data_length+index_length)/(1024*1024*1024),2),'G') total_size,
  round(sum(index_length)/sum(data_length),2) idxfrac
FROM information_schema.TABLES
WHERE table_schema not in ('mysql', 'performance_schema', 'information_schema')
GROUP BY engine
ORDER BY sum(data_length+index_length) DESC LIMIT 10;

结果:

Engine  Table rows data idx total_size idxfrac
InnoDB  9   288.85M 36.28G  58.59G  94.87G  1.61

该机器只有3.5GB的内存,设置为1GB用于Mysql

mysql sql database optimization
2个回答
3
投票
  • 保留股票代码。 (这是什么?idcompany ??)
  • 扔另一个idcompany
  • 建立另一个公司表 - ticker,company_name等。这解决了几个数量级的第一个查询。
  • 更改为PRIMARY KEY(ticker, date)这将比简单的INDEX(ticker, date)好几个数量级。为什么?因为第二个查询的所有行彼此相邻。也就是说,你不会在SELECT *的磁盘上弹跳。

但是,你应该停在那里。

缩小数据类型有助于避免代价高昂的I / O ......

  • DOUBLE(m,n)很糟糕。我希望他们能摆脱那种语法。它导致两个舍入。或者说只是DOUBLE或与DECIMAL做点什么。
  • 对于price ...感谢BRK,您需要在小数点左侧至少有6位数字。感谢“便士”或近乎退市的股票,您需要几个小数位。也许DECIMAL(12, 6)会这样做?这需要6个字节(相比之下,DOUBLE为8个字节)。
  • 对于volume ...一些指数基金可能超过40亿,所以INT UNSIGNED是不够的。也许你需要一个8字节的DOUBLE,或者用一个4字节的FLOAT失去一些精度。或者去一些DECIMAL
  • created_atupdated_at - 他们基本没用。它们需要10个字节。

其他查询怎么样?特别是,您每天会为每个自动收报机添加一个新行?我建议的PK会起作用,但速度很慢。而且,由于你整晚都在进行插入,所以不要添加另一个索引。

如果您有任何其他疑问,那么我们可以考虑PARTITIONs。到目前为止,分区只会减慢速度。

不要担心“向后索引扫描”。查询的主要成本是I / O,我已经解决了这个问题。

请注意您的每个查询现在需要如何触摸某些表中的连续行。这与扫描巨大的表(您的查询1)或在索引和数据(查询2)之间徘徊相反。因此,无论缓存有多“冷”,我的设计对于两个查询都会快得多。

PS。不需要二级索引。


2
投票

分区不如使用索引灵活。例如,请记住https://dev.mysql.com/doc/refman/5.7/en/partitioning-limitations-partitioning-keys-unique-keys.html上记录的规则:

“表中的每个唯一键必须使用表的分区表达式中的每一列”

这意味着您的分区不能使用company列,因为该列不是表的主键的一部分。

mysql> ALTER TABLE historical_pricing PARTITION BY KEY(company) PARTITIONS 37;
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function

相反,我建议你创建一个复合索引。这取代了company上的单列索引。

ALTER TABLE historical_pricing 
  DROP KEY company_idx, ADD KEY (company, date);

这有助于优化两个查询:

mysql> EXPLAIN SELECT DISTINCT company FROM historical_pricing;
+----+-------------+--------------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table              | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | historical_pricing | NULL       | index | company       | company | 19      | NULL |    1 |   100.00 | Using index |
+----+-------------+--------------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+

mysql> EXPLAIN SELECT * FROM historical_pricing WHERE company = 'AAPL' ORDER BY date DESC;
+----+-------------+--------------------+------------+------+---------------+---------+---------+-------+------+----------+----------------------------------+
| id | select_type | table              | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra                            |
+----+-------------+--------------------+------------+------+---------------+---------+---------+-------+------+----------+----------------------------------+
|  1 | SIMPLE      | historical_pricing | NULL       | ref  | company       | company | 15      | const |    1 |   100.00 | Using where; Backward index scan |
+----+-------------+--------------------+------------+------+---------------+---------+---------+-------+------+----------+----------------------------------+

(注意“后向索引扫描”是MySQL 8.0中的优化.MySQL 5.7也可以向后扫描索引,但成本较高。请参阅https://mysqlserverteam.com/mysql-8-0-labs-descending-indexes-in-mysql/

© www.soinside.com 2019 - 2024. All rights reserved.