一份关于上亿级别单表测试记录,大家可以看看找到优化方案和灵感
这张表大约有1.1亿数据, 是一个订单表,整张表数据22gb;使用mysql8
表格式如下:
CREATE TABLE `cp_orders` (
`code` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键(订单号)',
`a_code` int(11) DEFAULT NULL COMMENT '代理商id',
`u_code` int(11) NOT NULL COMMENT '用户id',
`trade_no` int(11) DEFAULT NULL COMMENT '订单编号',
`serial_number` varchar(70) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '',
`pay_type` tinyint(4) NOT NULL COMMENT '',
`pay_money` int(11) DEFAULT NULL COMMENT '支付金额',
`remarks` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
`close_date` datetime DEFAULT NULL COMMENT '',
`charge_model` tinyint(3) DEFAULT NULL COMMENT '',
`ext` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '',
`companyId` int(11) DEFAULT NULL COMMENT '',
`order_type` tinyint(4) NOT NULL COMMENT '',
`equipment_id` int(11) DEFAULT '0' COMMENT '',
`powerbank_id` int(11) DEFAULT NULL COMMENT '',
`type` tinyint(1) DEFAULT '1' COMMENT '',
`paid` int(2) DEFAULT '1' COMMENT '1:未支付,2已支付',
`out_time` tinyint(2) DEFAULT '1' COMMENT '',
`consume_type` tinyint(1) DEFAULT '1' COMMENT '',
`consume_scheme` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '',
`status` tinyint(4) NOT NULL COMMENT '',
`get_mcode` int(11) DEFAULT NULL COMMENT '',
`repay_mcode` int(11) DEFAULT '0' COMMENT '',
`create_date` datetime NOT NULL COMMENT '创建时间',
`update_date` datetime NOT NULL COMMENT '更新时间',
`trans_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '',
PRIMARY KEY (`code`) USING BTREE,
KEY `acode` (`a_code`)
) ENGINE=InnoDB AUTO_INCREMENT=179418339 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='订单表';
这里提出几个常见的业务方向进行索引优化:
基于以上两点为进行的订单查询,第一就肯定要把a_code添加进索引,
由于代理商进行查询时一般会根据订单状态,支付状态,时间范围进行查询,而且一般管理后台都会统计查询的订单额。
所以这里暂定索引为a_code
,添加索引后,亿级别数据getById是完全没有性能问题的。没有其他条件的话,分页的效率也还是不错
所有查询基本返回响应再1S内,还能接受。
接着抛出一个很有有意思的事情,以下sql在业务中做金额统计很常见:
# 耗时1.9s,扫描行数333446
select sum(pay_money) from cp_orders where a_code = 7903 limit 10000
# 耗时30+未出结果,扫描行数333446
select sum(pay_money) from(
select pay_money from cp_orders where a_code = 7903 limit 10000
) t
上面两个SQL看起来都一样,但是后者比前者执行效率高很多倍,特别是当订单数多的时候。后面去找到原因是,第一个sql会先将33W行数据加载
出来后,再取前1W行进行sum, 而后者则是直接取1W数据然后sum.
看下以下效率:
# 11.6s 反应很慢
select * from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
limit 100
# 12.2s 仅仅添加了状态
select * from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
and order_type = 1
limit 100
# 这里尝试统计下金额, 30.2s
select sum(pay_money) from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
and order_type = 1
limit 100
# 还记得前面的结论把,使用第二种方式统计, 10.4s
select sum(pay_money) from (
select pay_money from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
and order_type = 1
limit 100
) t
看起来此时查询很慢,此时我们加上时间索引看下改善情况,也就是索引变为a_code, create_date
增加索引耗时约5分钟。接着再看上面sql执行时间:
看下以下效率:
# 0.137s 这个就基本没啥毛病了
select * from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
limit 100
# 0.106, 此时看起来就没有之前那么离谱了。 仅仅添加了状态条件
select * from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
and order_type = 1
limit 100
# 这里尝试统计下金额, 17.51s。 这里看起来已经比之前30s快了很多了
select sum(pay_money) from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
and order_type = 1
limit 100
# 0.075s, 这么看的话似乎就没什么毛病了,可以直接用了
select sum(pay_money) from (
select pay_money from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
and order_type = 1
limit 100
) t
看起来此时查询很慢,此时我们加上支付金额索引看下改善情况,也就是索引变为a_code, create_date, pay_money
增加索引耗时约5分钟。接着再看上面sql执行时间:
看下以下效率:
# 这里尝试统计下金额, 21.51s。我擦,加了金额看起来没什么效果
select sum(pay_money) from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
and order_type = 1
limit 100
# 0.145s, 我去,加了反而比每加更慢
select sum(pay_money) from (
select pay_money from cp_orders where a_code = 7903
and create_date >= '2019-06-26 12:38:18'
and create_date < '2020-06-26 12:38:18'
and order_type = 1
limit 100
) t
其次则是管理后台查询,管理后台和代理商端查询有天然的不同。代理商端查询都会加上代理商id这个基本参数
而管理后台是全面的,基本上能确定的是一定会带上时间的。这里也是所有系统都推荐的。
所以对于管理后台来说应该增加的索引是:
员工+时间
订单状态+时间
代理商+时间
订单编号
可能会问,为什么不给时间增加一个索引,其实对于全部查询来说,直接使用id倒排效果几乎和时间倒排是一样的。
这里总结下:
创建索引:
每次约5分钟
删除索引:
几乎是及时删除,无任何影响。
关于锁表
在mysql8中,使用alert table 进行索引创建时将会锁表,使用create index 则不会