架构师眼中的CRUD:分库分表与数据查询那些事(下)

声明

下面的故事,记录的技术要点,是真实发生在我身上的。为了记录下这些知识点,同时让大家以一个放松的心态去进行阅读,将其改编成诙谐幽默的小故事。

如有雷同,纯属巧合。

本期登场人物介绍

C大:我们的架构师,技术涉猎面广,考虑问题全面
小L:刚入职公司的应届毕业生开发,对技术有着无比的热情~
R帅:标标准准的帅哥一枚,资深前端开发~

前情提要

上回飞机

上回中,小L针对分布式系统中,对于优惠券的查询做了大量工作。实现了只采用关系型数据库的前提下的分表数据多维度查询方案。
通过将一份数据入主表后,再推送到消息队列,通过异步双写服务,将数据写入从库的其他数据维度表。
总体架构如下:
在这里插入图片描述

第二天上班以后…

小L昨天从C大那里又学到一招数据库双写的绝招以后,精神倍儿爽,昨晚睡得甭提有多香了。这不,一大早就到了公司,总结了一下昨天的收获。这不等到上班以后,就一个劲儿地催着R帅来联调。R帅被催的不耐烦了,说:“等我把还有点点页面写完嘛。既然你这么着急,我就先看看接口文档。”
看了一会后,R帅就问了个问题:“你这个接口没法用啊,页面上好多东西都缺的!”,
小L:“啊?怎么会,我可是按着原型来看的,而且为了解决多维度查询的问题,费了老大的功夫呢!”
R帅叹了口气:“你自己看嘛,比如我要查询一段时间内发放的优惠券,你接口里没有告诉我数据总共有多少,我怎么做产品原型里下面的翻页符号呢?”

让我们一起来看一下小L的接口文档(这里做了简化):
API版本1

小L的接口问题在哪儿呢?再来看一看我们的原型:
在这里插入图片描述
确实,在下面,我们看到了页面按钮。前端要实现这个按钮,首先要知道自己当前页面编号(这个没问题,前端肯定知道,常用的做法是在URL中带上)。第二,要知道还剩下多少页,这个一般的做法是,查询以后,后端把总共有多少条数据,以及当前一页的数据返回。

这下,小L明白自己的问题了:“哦,这里是我疏忽了,马上加上。”
没一会功夫,第二个版本来了:
在这里插入图片描述
R帅回答说:“这下没问题了,我可以按照这个开发,等我下午给你联调吧。”
小L心想,我这都写完了,不就是就加个总量字段就行,用不了到下午,一会就来催你,O(∩_∩)O哈哈~

快到中午吃饭的时候…

“弄得怎么样啦?我这已经写好了,下午能联调吗?” R帅显然已经完成了自己的代码。
“嗯,额…等下…这边还有点问题。” 小L有些吞吞吐吐。

这又是怎么回事呢?一个总量,怎么就把小L给难倒了。

一般,我写单表分页的时候,是比较简单的。只需要先select count(*) from table where …,查询出数据的总量,如果有数据的话,使用select column1, column2, … from table where … limit 1,10 这样就能获取到特定条件下,从第一页的10条数据。
但是,由于我们的小L对数据做了分表操作,有可能数据落在了多张表里面。举个例子,我们以时间维度的分表,一般是到天,一天一张数据表,那么在跨天范围查询的时候,就会出现数据在多表中的情形。这时候要获取数据总量,就需要查询多张表。

小L一上午都在解决这个问题,他尝试了下面的两种方法:

  1. 使用多表union,如果查询3月20日至3月23日4天的数据,需要使用到类似这样的查询。
select count(*)
from (
   select 1 from table_20200320
   union all 
   select 1 from table_20200321
   union all 
   select 1 from table_20200322
   union all 
   select 1 from table_20200323
) t

但这个方案,很快就被小L自己否定了,如果查15天或者更多呢?想不出来这会是多大的一个灾难哦。

  1. 每一张表count(*) 以后,把总量加起来。

但这个方案,和方案1的区别也大不了多少,查询速度还是很慢。总之,小L对这个方法也不满意!

小L纠结了一上午,也没折腾出个所以然来。弄得小L又开始怀疑人生了,怎么越来越整不明白了,连个count(*)都写不好了。。。

R帅看小L这么纠结,甩下一句话:“不急,你什么时候好了叫我,我们调。反正我手头上还有别的活。”然后就吃饭去了。

午餐时间

到了吃饭时间,小L却提不起兴致来,他还在思考早上的那个问题。【加了一个字段引发的血案】用这句话来形容,再贴切不过了。
看着小L一筹莫展,刚好一起来吃饭的C大坐到了小L的对面,问他今天遇到啥难题了。小L把事情跟C大说了以后。“其实我昨天就猜你会遇到这个事情,只不过是让你经历一下,自己思考得出的结论,比我直接告诉你要强。” C大笑着说。
“其实呢,你的想法是没错的,如果要获取数据的总量,由于你的数据是分散在多个表下的,因此你也只能通过遍历查询来获取数据总量。” C大接着说。

确实,当我们对数据分表以后,要统计数据的总量,是一件比较难的事情。也许有的同学会说了,那我们每天跑批去统计一下数据总量就可以了嘛。
但是,我们的查询条件往往是变化的,所以说,很难去做一个穷举的统计。
因此,在这种业务场景下,如果要获取数据总量,唯一的办法,就是做多表的联合或者遍历统计。

小L似乎明白自己已经走进死胡同了,于是问:“那就么有别的什么办法了么?”
C大:“有!”。
“但你刚才不是说,只能通过遍历的方法来获取数据总量吗?现在又说有办法,难道真的要写一个遍历查询啊!那样的话,如果单表数量过百万怎么办?性能难道不用考虑吗?”小L不依不饶地继续问。
C大被小L的精神给折服了,“哈哈,服了你了,怎么就这么轴呢?谁说一定要查询数据的总量的啊?产品有给你提这个需求吗?”

开发同学小贴士:
这里说的是开发同学的故事。我当然希望广大开发者能够在我这里有所得,那样我会很开心的。
说到这里,大家不知道有没有跟上C大的思路哈,C大说这句话到底是什么意思呢?大家不妨再回到上面的原型里面看看产品经理给我们提的需求。
产品经理需要实现的是,一个带有分页展示的底栏,可以显示当前出于第几页,后面有几页可以点,附带一个跳转到X页的功能。
这是我们要实现的功能,只是我们开发同学的惯性,把这个需求很自然地翻译成了接口原语:我要返回当前页,页面容量,总计条数。
虽然这个在单表查询的情况下,作为我们分页查询的一个通式,但并非题目的唯一解法。

根据我架构师的从业经验。很多时候,我更加喜欢把产品经理提的需求,当做是给开发同学出的一道题目,让我们开发同学,使用接口语言,程序语言,SQL语言等多种语言来解答这道题目。
做过数学题的大家肯定明白一个道理,有些数学题不止一种解法。做需求也是一样,我们要把自己的所见所学,都用上来完成这个需求,当然实现这个需求的方式,有时候并不止一种。
好了,说了这么多,我想大家应该明白我们的C大想表达什么意思了,那让我们接着看下面的故事吧。

C大接着说:“事实证明,不借助其他的工具,只依靠关系型数据库,我们要获取总量是相当困难的。那么我们就要尝试另一种不获取总量,依然能实现分页查询需求的办法。”
小L:“还有另外解法吗?呜,这我真的不知道了。”
“不知道没有关系”,C大说,“我们先把饭吃完,然后出去遛个弯,我跟你絮叨絮叨。”

饭后遛弯

C大与小L一起在公司旁边的小河边遛弯,给小L讲解分页查询在直接获取数据总量性能欠佳的情况下的一种迂回方案。
下面是C大给出的解法:

方案的大体流程是这样的。首先,需要对接口进行重新定义,重新定义的接口,长得应该是类似这个样子的:
在这里插入图片描述
OK,现在我们的前端同学就可以很happy地完成分页按钮的功能了。而且,我们发现这样做的一个好处就是,前端顺带做了预加载数据的功能。我们发现,后台会一次性返回前端请求的startPage - endPage之间的所有数据。所以,用户在切换这些页面之间的数据时,前端可以做到秒切。同时,我们也不用再解决数据总量的问题了。

说到这里,我想一定有钻牛角尖的同学要问了,那如果前端查询了从1-999页的数据,也得一次性给返回啊?
这其实很简单,可以在接口文档中约定,一次最多预加载多少页。或者还可以更新一下接口,指定起始加载页以及预加载页数这两个参数就够了。

那么,跳转到多少页的功能怎么做呢?其实,指定上面接口中的startPage就可以了。明白了原理以后,我们来看一下查询流程吧。
在这里插入图片描述

从中,我们看到,这里的设计充分运用了惰性加载的思想,即:用到了再加载。什么意思呢?
比如我们查询15天的优惠券数据,那么就有15张按天分类的表。如果采用积极加载的方式,我们肯定是要把这15张表都遍历一遍。那么现在采用惰性加载的方式,比如我只需要获取200条数据。
假设一天的数据量为30万的情形下,那么我们其实只需要从第一条数据开始,查出200条即可,完全没必要把15天的数据都查询一遍。
其实这个方案的最核心思想,就是惰性加载。按照前端的请求,来对数据进行预判,在对应的表中找到需要的数据并返回。
当然, 这个方案仍然有优化的空间,这里不再展开,只抛砖引玉:比如要获取末尾几条数据的时候,我们是否可以将主键倒排,从末尾加载这几条数据,然后在内存中,将查询出来的数据顺序调整回来。

下午,小L按照C大的思路,花了点时间,把数据查询算法给实现了。一番测试后,如果数据在比较前面的位置,查询性能还是比较满意的。除了数据在分页的后面,需要遍历多表才能定位到数据位置的时候性能比较差。

确实,在分页中后部分的查询,mysql的查询性能会比较差。至少就我目前掌握的知识面来看的话,这是纯靠关系型数据库进行查询的一个难以解决的问题。如果有思路的小伙伴,也可以留言给我进行交流。

总结

今天的故事,主要给大家讲述了两个方面的内容。

  1. 开发人员开发需求时,一定不要有先入为主的观念。往往我们换个思路就可以用一种更好的方式去实现一个需求。这也对我们的知识面提出了更高的要求。
  2. 分享给大家有别于通用写法的一种分页查询的写法。

至此,我们的《分库分表与数据查询那些事》这个专题故事到这里就结束了。后面还会有一个与此关联的彩蛋给到大家。讲的是我们的云架构下,是如何去解决海量数据多维度查询的。那么,我们下期再见吧~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值