亿级索引优化和查询技巧优化

一份关于上亿级别单表测试记录,大家可以看看找到优化方案和灵感

这张表大约有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='订单表';

这里提出几个常见的业务方向进行索引优化:

  1. 对于订单来说, 主要是代理商端和商户端订单查询。第二是订单额的统计

基于以上两点为进行的订单查询,第一就肯定要把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 则不会

你可能感兴趣的:(java,前端,数据库)