Payment提现申请定时任务执行速度慢分析

根据线上反馈目前Payment提现申请定时任务(以下简称定时任务)运行时间较长。通过对定时任务所执行的SQL进行分析发现,定时任务执行速度慢是因为account_journal表和r_settlement_journal表的关联查询速度过慢造成。

两张表,每张表都有40W条数据,一张流水表(account_journal);一张中间表(r_settlement_journal),中间表记录着已经处理了的流水表数据。

流水表结构:CREATE TABLE `account_journal` (

`id` varchar(32) NOT NULL COMMENT '主键',

`account_id` varchar(32) NOT NULL,

`in_or_out` int(11) NOT NULL DEFAULT '0' COMMENT '出入账(0:入账 1:出账)',

`origin_amount` int(11) NOT NULL DEFAULT '0' COMMENT '原金额(单位:分)',

`service_charge` int(11) DEFAULT NULL COMMENT '服务费(单位:分)',

`actual_amount` int(11) NOT NULL DEFAULT '0' COMMENT '实际金额(单位:分)',

`account_amount` int(11) NOT NULL COMMENT '入账后,账户余额',

`create_time` datetime NOT NULL COMMENT '创建时间',

`info` varchar(256) DEFAULT NULL COMMENT '描述',

`opposite_type` int(2) NOT NULL COMMENT '交易对方类型:0.咕咕订单 1 商家结算',

`opposite_account_number` varchar(64) NOT NULL COMMENT '交易对方账号 当opposite_type=0时,为顾客手机号;当opposite_type=1时,为第三方支付账号',

`opposite_account_name` varchar(64) DEFAULT NULL COMMENT '交易对方账号姓名',

`opposite_account_type_name` varchar(64) NOT NULL COMMENT '交易对方账号类型:微信支付、支付宝、中国银行…',

`trade_id` varchar(32) NOT NULL COMMENT '交易ID当opposite_type=0时,为订单ID;当opposite_type=1时,为结算ID',

`business_id` varchar(32) DEFAULT NULL COMMENT '当opposite_type=0时,为支付id或者退款id;当当opposite_type=1时为空',

`create_time_ms` bigint(20) DEFAULT NULL COMMENT '流水创建时间按毫秒存储',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=gbk;

中间表结构:CREATE TABLE `r_settlement_journal` (

`id` varchar(32) NOT NULL COMMENT '主键',

`journal_id` varchar(32) NOT NULL COMMENT '流水id',

`settlement_id` varchar(32) NOT NULL COMMENT '审核id',

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=gbk;

每次定时任务执行时需要查询出没有处理的流水表数据。

查询语句使用not exists进行关联查询-- EXPLAIN

SELECT

SUM(a.actual_amount) amount

FROM

account_journal a

WHERE

NOT EXISTS (

SELECT

b.journal_id

FROM

r_settlement_journal b

WHERE

b.journal_id = a.id

)

AND a.in_or_out = 0

AND a.opposite_type = 0

AND a.account_id = '58077112a5a911e5b45900215edb4bc4'

AND a.create_time < '2016-07-26 23:59:59'

40W条数据查询用时为26s,如果切换成left join用时28s。

查询语句分析结果:*************************** 1. row ***************************

id: 1

select_type: PRIMARY

table: a

type: ALL

possible_keys: NULL

key: NULL

key_len: NULL

ref: NULL

rows: 421952

Extra: Using where

*************************** 2. row ***************************

id: 2

select_type: DEPENDENT SUBQUERY

table: b

type: ALL

possible_keys: NULL

key: NULL

key_len: NULL

ref: NULL

rows: 386560

Extra: Using where

两张表都使用了全表扫描。

优化步骤:

1. 中间表关联id(journal_id)加索引ALTER TABLE `r_settlement_journal`

ADD INDEX `idx_journal_id` (`journal_id`) USING BTREE ;

执行查询语句,查询时间由26s变为0.6s,查询效率提升明显。

2. 流水表account_id字段添加索引ALTER TABLE `account_journal`

ADD INDEX `idx_account_id` (`account_id`) USING BTREE ;

再次执行查询语句,查询时间由0.6s变为0.005s,查询效率再次提升。

3. 添加索引对插入效率的影响分析

3.1 表中已有40W数据时,有索引和无索引插入时间对比account_journal表有account_id索引和无account_id索引插入时间对比

|测试数据|无索引插入数据耗时 |有索引插入数据耗时 |

|——-|——————-|——————-|

|8线程同时插入1W条数据 ,共插入8W条数据| 77.19s| 78.413s|

|64个线程,每个线程插入10条数据,共插入640条数据|0.504s|0.536s|

r_settlement_journal表有journal_id索引和无journal_id索引插入时间对比

|测试数据|无索引插入数据耗时 |有索引插入数据耗时 |

|——-|——————-|——————-|

|8线程同时插入1W条数据 ,共插入8W条数据| 56.29s|57.3s|

|64个线程,每个线程插入10条数据,共插入640条数据|0.393s|0.458s|

3.2 表中无数据时,有索引和无索引插入时间对比account_journal表有account_id索引和无account_id索引插入时间对比

|测试数据|无索引插入数据耗时 |有索引插入数据耗时 |

|——-|——————-|——————-|

|8线程同时插入1W条数据 ,共插入8W条数据| 54.601s| 55.599s|

r_settlement_journal表有journal_id索引和无journal_id索引插入时间对比

|测试数据|无索引插入数据耗时 |有索引插入数据耗时 |

|——-|——————-|——————-|

|8线程同时插入1W条数据 ,共插入8W条数据| 51.833s|52.505s|

总结:有无索引对数据插入效率影响不大,但是能显著的提升关联查询的效率。

优化结果

提现申请定时任务执行一次所需时间由线3小时变为8分钟。

(测试数据:40W条数据,处理2016-07-25当天的流水数据)

添加索引:-- ALTER TABLE `r_settlement_journal` ADD INDEX `idx_journal_id` (`journal_id`) USING BTREE ;

-- ALTER TABLE `account_journal` ADD INDEX `idx_account_id` (`account_id`) USING BTREE ;

ALTER TABLE `r_settlement_journal` ADD INDEX `idx_journal_id` (`journal_id`) ;

ALTER TABLE `account_journal` ADD INDEX `idx_account_id` (`account_id`) ;

删除索引:DROP INDEX idx_journal_id ON r_settlement_journal;

DROP INDEX idx_account_id ON account_journal;