分布式数据库下的大表分页查询

我们对外提供的/video/list接口会对某用户下所有的视频列表进行分页查询,而vod_video表是一个具有亿级数据量的大表,任何一个对此表的查询都应该慎重对待。

分布式数据库大表查询的常见问题

vod_video表的schema像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE TABLE `vod_video` (
`vid` bigint(20) NOT NULL COMMENT '视频ID,自增主键',
`video_name` varchar(256) DEFAULT NULL COMMENT '视频文件名称',
`description` varchar(256) DEFAULT NULL COMMENT '视频简介',
`duration` bigint(20) DEFAULT NULL COMMENT '视频时间长度(秒)',
`width` int(11) DEFAULT '0' COMMENT '视频宽度',
`height` int(11) DEFAULT '0' COMMENT '视频高度',
`initial_size` bigint(20) DEFAULT NULL COMMENT '原始视频文件大小',
`uid` bigint(20) DEFAULT NULL COMMENT '视频所属用户的id',
`type_id` bigint(20) NOT NULL COMMENT '视频所属的分类ID',
`create_time` bigint(20) NOT NULL COMMENT '视频创建时间',
`update_time` bigint(20) NOT NULL COMMENT '视频更新时间',
`db_create_time` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
`db_update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`vid`),
KEY `idx_uid_ctime` (`uid`, `create_time`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='视频表' /* BF=vid, POLICY=user, STARTID=8001, AUTO_INCREMENT_COLUMN=vid, ASSIGNIDTYPE=USB */;

由于均衡字段vid的存在,vod_video表中的数据得以相对均匀地分布在各个数据节点(dbn)上,当客户端查询select from vod_video order by create_time desc limit n offset m时,QS会向每个dbn发送select from vod_video order by create_time desc limit n+m,也就是说每个dbn节点最多会返回(n+m)条数据,所有返回的数据会在QS再做一次merge sort,如果(n+m)很大,那么merge sort的数据集就会很大,一方面会增加排序时间,另一方面也会使QS的网卡流量瞬间飙升,甚至可能超过最大阈值。

ddb架构图

如果按照上面做法,直接将limit n offset m丢给ddb,当用户的查询分页数很大,而且用户下的视频量很大时,每个dbn的返回数量会非常大,严重影响性能。所以需要进行优化。

下面说明优化后的做法。

跳跃式查询

假设有个查询场景,用户查的offset是3万,limit是500,为避免offset过大,我们先查3次,每次的offset是1万limit是1,定位到3万offset的起始位置,然后把这个起始位置带到where条件里面再查下面的500条,如下图所示(图中v1~v30500按视频创建时间create_time降序排列)

跳跃式查询

我们的目标是拿到v30001~v30500的数据。

  • 首先,select vid, create_time from vod_video where create_time<=32503651200000 limit 1 offset 9999拿到v10000;
  • 然后,select vid, create_time from vod_video where create_time<=c1 limit 1 offset 10000拿到v20000;
  • 接着,select vid, create_time from vod_video where create_time<=c2 limit 1 offset 10000拿到v30000;
  • 最后,select * from vod_video where create_time<=c3 limit 500 offset 1拿到v30001~v30500

这其实是利用了create_time进行跳跃式查询,逐渐逼近v30000,最后再一次取出目标数据。每次查询的offset都不超过10000,不会出现offset过大导致的查询性能下降以及QS网卡流量过大的问题。

当然,这种方法有一个小瑕疵,就是如果有create_time相等的视频,那么每次分页取出的视频可能会有少部分重叠。一方面,create_time是毫秒级别的,所以这种情况产生的概率比较小;另一方面,这种情况其实也相当于是为了提升查询效率的一个tradeoff,在概率较小的情况下是可以接受的,而且用户也可以做一次根据vid的去重来处理这种情况。