在数据库中,我有一个包含订单商品的表格。该表包含大约3600万条记录。
运行这样的查询需要 3分钟:
SELECT COUNT(DISTINCT DATE(created_on), product_id) FROM order_items;
运行这样的查询需要 13秒:
SELECT COUNT(1) FROM order_items;
有些东西告诉我,有3600万条记录没那么多,而且两个查询的运行都相当缓慢。
在这里开始研究性能问题的清单是什么?
我们正在使用MySQL(实际上是它的Clustrix版本,MySQL 5.0.45-clustrix-6.0.1
)。
修改即可。添加更多信息:
/* SHOW CREATE TABLE order_items; */
CREATE TABLE `order_items` (
`id` int(10) unsigned not null AUTO_INCREMENT,
`state` enum('pending','sold_out','approved','declined','cancelled','processing','completed','expired') CHARACTER SET utf8 not null default 'pending',
`order_id` int(10) unsigned not null,
`product_id` int(10) unsigned not null,
`quantity` smallint(5) unsigned not null,
`price` decimal(10,2) unsigned not null,
`total` decimal(10,2) unsigned not null,
`created_on` datetime not null,
`updated_on` datetime not null,
`employee_id` int(11),
`customer_id` int(11) unsigned not null,
PRIMARY KEY (`id`) /*$ DISTRIBUTE=1 */,
KEY `updated_on` (`updated_on`) /*$ DISTRIBUTE=1 */,
KEY `state` (`state`,`quantity`) /*$ DISTRIBUTE=3 */,
KEY `product_id` (`product_id`,`state`) /*$ DISTRIBUTE=2 */,
KEY `product` (`product_id`) /*$ DISTRIBUTE=1 */,
KEY `order_items_quantity` (`quantity`) /*$ DISTRIBUTE=2 */,
KEY `order_id` (`order_id`,`state`,`created_on`) /*$ DISTRIBUTE=3 */,
KEY `order` (`order_id`) /*$ DISTRIBUTE=1 */,
KEY `index_order_items_on_employee_id` (`employee_id`) /*$ DISTRIBUTE=2 */,
KEY `customer_id` (`customer_id`) /*$ DISTRIBUTE=2 */,
KEY `created_at` (`created_on`) /*$ DISTRIBUTE=1 */,
) AUTO_INCREMENT=36943352 CHARACTER SET utf8 ENGINE=InnoDB /*$ REPLICAS=2 SLICES=12 */
和
/* SHOW VARIABLES LIKE '%buffer%'; */
+----------------------------------------+-------+
| Variable_name | Value |
+----------------------------------------+-------+
| backup_compression_buffer_size_bytes | 8192 |
| backup_read_buffer_size_bytes | 8192 |
| backup_write_buffer_size_bytes | 8192 |
| mysql_master_trx_buffer_kb | 256 |
| mysql_slave_session_buffer_size_events | 100 |
| net_buffer_length | 16384 |
| replication_master_buffer_kb | 65536 |
+----------------------------------------+-------+
编辑2 。这两个查询的EXPLAIN
语句:
mysql> EXPLAIN SELECT COUNT(1) FROM order_items;
+----------------------------------------------------------+-------------+-------------+
| Operation | Est. Cost | Est. Rows |
+----------------------------------------------------------+-------------+-------------+
| row_count "expr1" | 29740566.81 | 1.00 |
| stream_combine | 26444732.70 | 32958341.10 |
| compute expr0 := param(0) | 1929074.80 | 2746528.43 |
| filter isnotnull(param(0)) | 1915342.16 | 2746528.43 |
| index_scan 1 := order_items.order_items_quantity | 1854308.19 | 3051698.25 |
+----------------------------------------------------------+-------------+-------------+
5 rows in set (0.13 sec)
和
mysql> EXPLAIN SELECT COUNT(DISTINCT DATE(created_on), product_id) FROM order_items;
+----------------------------------------------------------------------------------+-------------+------------+
| Operation | Est. Cost | Est. Rows |
+----------------------------------------------------------------------------------+-------------+------------+
| hash_aggregate_combine expr1 := count(DISTINCT (0 . "expr0"),(1 . "product_id")) | 10115923.36 | 4577547.38 |
| hash_aggregate_partial GROUPBY((0 . "expr0"), (1 . "product_id")) | 3707357.04 | 4577547.38 |
| compute expr0 := cast(1.created_on, date) | 2166388.20 | 3051698.25 |
| index_scan 1 := order_items.__idx_order_items__PRIMARY | 2151129.71 | 3051698.25 |
+----------------------------------------------------------------------------------+-------------+------------+
4 rows in set (0.24 sec)
答案 0 :(得分:0)
第一个查询必须遍历整个数据库,检查表中的每一行。 created_on和product_id上的索引可能会显着加快速度。如果您不了解索引,http://use-the-index-luke.com是一个很好的起点。
在我看来,第二个查询应该是即时的,因为它只需检查表元数据而不需要检查任何行。
答案 1 :(得分:0)
有些注意事项:
如果添加INDEX(product_id, created_on)
,第一个查询应该运行得更快,因为它将覆盖索引"。 (字段的顺序可以相反。)
按照给定的顺序运行这两个查询可能会导致信息被缓存,从而使第二个查询运行得更快。
SELECT COUNT(*) FROM tbl
将使用最小的索引。 (在InnoDB中。)
如果你有足够的RAM,并且如果innodb_buffer_pool_size
大于表,那么一个或另一个操作可能完全在RAM中执行。 RAM比磁盘快很多。
请提供SHOW CREATE TABLE order_items;
我不得不猜太多
请提供SHOW VARIABLES LIKE '%buffer%';
。你有多少RAM?
修改强>
既然它是Clustrix,那么可能会发生根本不同的事情。这是一个猜测:
SELECT COUNT(1) ...
可能会分发到节点;每个节点都会得到一个小计;然后可以(非常迅速地)添加小计。SELECT COUNT(DISTINCT ...)...
实际上必须以某种方式查看所有行。也就是说,努力无法分配。 或许会发生的是所有行被铲到一个节点进行处理。我猜这是几GB的东西。 Clustrix有某种方法可以获得EXPLAIN
吗?我很想知道每个SELECTs
的内容。 (以及它是否支持我的猜测。)
我希望GROUP BY
和DISTINCT
在分片'中效率低下系统(如Clustrix)。
答案 2 :(得分:0)
您应该发布查询计划,但我怀疑要处理查询MySQL必须遍历product_id和created_on索引。对于created_on字段,它还必须聚合值(字段是日期时间,但是你想按日期分组)。如果你需要速度,我会添加额外的字段created_on_date只有日期,我会在product_id上创建一个索引created_on_date。它应该使您的查询更快。 当然count(1)查询更快,因为它根本不读取表,它可以使用索引元数据。
答案 3 :(得分:0)
在Plan中,使用了stream_combine。它只读取了索引(order_items_quantity
(quantity
))
一般来说,COUNT(DISTINCT ...)在RDB,NewSQL Scale-Out RDB中效率可能更低,这是因为难以减少节点间流量(在很多情况下应该将大量数据转发到GTM节点) )。所以Clustrix需要'dist_stream_aggregate'和正确的索引(列和列顺序)
在计划中,显示了hash_aggregate_partial。它扫描了FULL TABLE(__idx_order_items__PRIMARY
)并花了很多时间(更大的尺寸)
对于并行性,它可能不足以满足所有可用的cpu。 (即SLICES = 12)。我想知道每个节点有多少节点和cpus(?)
由于DATE(created_on
),索引created_at
(created_on
)无效。优化器(Plan)认为FULL TABLE SCAN比查找INDEX(created_at
)然后访问TABLE(__idx_order_items__PRIMARY
)更有效。
对于这种情况,我建议按以下方式进行测试。
create_on_date_type
order_items
上创建索引new_index(create_on_date_type
,productid
)
关于分配=? &安培; slices = ?,应该对你的数据集进行测试。(切片的数量可能会影响cpu并行性的工作量)dist_stream_aggregate
。
dist_stream_aggregate
只能使用查询的“new_index”列高效工作。我相信你可以获得更好的表现。