【面试】考官问我如何实现分库分表?
一、什么是窄表?什么是宽表?
1.1 窄表/宽表概念
在数据库中,窄表和宽表是两种设计思想,分别指的是列数少或者列数多的表格。
窄表是指只包含少量列(如主键和几个属性)的表格。这种设计思想用于解决单一业务场景下的数据存储及访问问题,从而提高查询效率,减少存储空间。通常应用于 OLTP(联机事务处理)这样的在线事务处理系统。关系型数据库中大部分的表都是窄表。
相比之下,宽表则是指具有大量列的表格。这些列可以来自不同的来源,可能包括各种聚合和汇总数据等。宽表用于存储冗长的数据,以支持分析和挖掘等复杂查询,通常应用于 OLAP(联机分析处理)这样的在线分析处理系统。宽表能够提供更为全面和详细的数据,但同时也会带来一定的复杂度,包括查询效率下降等问题。
窄表与宽表的选择取决于具体的业务需求和数据处理场景。需要根据实际情况进行评估、设计和优化。
1.2 宽表表解决了什么问题?
宽表可以解决一些需要同时获取多个数据属性、进行数据分析和数据挖掘的问题。相对于狭窄的表格,宽表可能会包含更多关联的信息,如不同维度、时间范围内的历史数据或聚合统计数据。这些信息在分析和预测任务中非常有用。同时,宽表减少了在多个表之间查找、联接数据等操作,简化了查询和检索的流程,提高了数据可访问性和可靠性,并且能够方便地支持 OLAP 和数据挖掘等高级数据处理操作。但是,需要注意的是,宽表对查询性能和储存空间也提出了一些挑战,需要根据具体情况进行优化和平衡。
1.3 什么是QPS
Queries Per Second,顾名思义:“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
1.4 什么是TPS
是TransactionsPerSecond的缩写,也就是事务数/秒。它是软件测试结果的测量单位。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。
每秒事务数,表示服务器每秒处理的事务数量。一个事务是指客户端向服务器发送请求并收到响应的过程,通常包括多个数据库操作。TPS衡量的是系统的整体吞吐量和事务处理能力
1.5 应用场景
- TPS:适用于需要确保事务完整性的场景,如金融交易、电商平台的订单处理等。在这些场景中,事务的原子性、一致性、隔离性和持久性(ACID属性)至关重要1。
- QPS:适用于对查询性能要求较高的场景,如搜索引擎、推荐系统等。在这些场景中,系统需要快速响应用户的查询请求,因此高QPS意味着更快的响应时间13。
1.6 优化方法
- 提高TPS:优化事务处理的效率,包括数据库优化(如索引、读写分离)、缓存策略、多线程并发处理等技术1。
- 提高QPS:优化查询效率和降低响应时间,方法包括建立高效的索引、使用更快的查询算法、实施缓存机制等13。
综上所述,TPS和QPS在定义、应用场景和优化方法上各有侧重,了解它们的不同有助于更好地评估和优化数据库性能。
二、分库分表
2.1 什么是分库分表?
分库:从单个数据库拆分成多个数据库的过程,将数据散落在多个数据库中。
分表:从单张表拆分成多张表的过程,将数据散落在多张表内。
2.2 为什么分库分表?
随着平台的业务发展,数据可能会越来越多,甚至达到亿级。以MySQL为例,单库数据量在5000万以内性能比较好,超过阈值后性能会随着数据量的增大而明显降低。MySQL单表数据量是500w-1000w之间性能比较好,超过1000w性能也会下降。这就会导致查询一次所花的时间变长,并发操作达到一定量时可能会卡死,甚至把系统给拖垮。
2.2.1 索引的极限
单表数据量达到几十万或上百万以上,使用索引性能提升也不明显。
2.2.2 分表的边界
分表使用门槛:单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
一般是单个库数据量太大,从而导致IO请求的瓶颈。
TPS太高通过 读写分离 无法解决,需要水平横向扩展数据库,
解决QPS过高和并发写DB的压力(参考redis分片集群)
或者单个数据库连接数不够。
2.2.3 分库分表策略
如果是写请求TPS太高可能需要分库,那读请求QPS太高一般怎么处理?
1,加缓存。如果是某些固定且经常读数据可以加,但是某些比如limit这种读就不方便了。
2,MySQL读写分离,用主库写,从库读。
3,读写分离也不行,才进行分库。
2.3 水平分库
以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中。
场景:系统绝对并发量上来了,分表难以根本上解决问题,并且没有明显的业务归属来垂直分库。
- 每个库的结构都一样;
- 每个库的数据都不一样,没有交集;
- 所有库的并集是全量数据;
2.4 垂直分库
2.5 水平分表
2.6 垂直分表
2.7 分库分表的策略和方法
什么是数值范围法
按照时间区间或ID区间来切分。例如:按日期将不同月甚至是日的数据分散到不同的库中;将userId为1~9999的记录分到第一个库,10000~20000的分到第二个库,以此类推。某种意义上,某些系统中使用的“冷热数据分离“,将一些使用较少的历史数据迁移到其他库中,业务功能上只提供热点数据的查询,也是类似的实践。
这样的优点在于:
- 单表大小可控
- 天然便于水平扩展,后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移
- 使用分片字段进行范围查找时,连续分片可快速定位分片进行快速查询,有效避免跨分片查询的问题。
缺点:
- 热点数据成为性能瓶颈。连续分片可能存在数据热点,例如按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询
Hash取模(对表/库 的总数进行hash取模)
一般采用hash取模mod的切分方式,例如:将 Customer 表根据 cusno 字段切分到4个库中,余数为0的放到第一个库,余数为1的放到第二个库,以此类推。这样同一个用户的数据会分散到同一个库中,如果查询条件带有cusno字段,则可明确定位到相应库去查询。再比如说有用户表user,将其分成3个表user0,user1,user2.路由规则是对3取模,当uid=1时,对应到的是user1,uid=2时,对应的是user2.
优点:数据分布均匀
缺点:数据量大后,数据重新分表后迁移的时候麻烦;不能按照机器性能分摊数据 。
解决建议:先同步到大数据平台,然后在大数据平台重新根据分片算法路由到各个表,最后同步给业务平台mysql数据表
什么是基因法,有什么方案?(复合分片算法)
基因法是将分片键的信息,融入到另一个分片字段B中。这样如果通过字段B查询,可以通过字段B解析出分片键,得到分片键就能定位具体的分表。(shardingsphere如何操作,待操作)
淘宝订单号,把用户的后六位数据冗余到订单号里。这样的话,我们就可以按照用户 ID 后六位进行分库分表,并且将分片键定义为用户 ID 和订单号。(用户ID和订单号都算分片键了)
具体原理如下:
写入时,基因法生成user_id,如图所示。关于xbit基因,例如要分8张表,23=8,故x取3,即3bit基因。根据user_id查询时可直接取模路由到对应的分库或分表。根据user_name查询时,先通过user_name_code生成函数生成user_name_code再对其取模路由到对应的分库或分表。id生成常用snowflake算法。
什么配置路由
配置路由就是路由表,用一张独立的表来记录路由信息。同样以用户 ID 为例,我们新增一张 user_router 表,这个表包含 user_id 和 table_id 两列,根据 user_id 就可以查询对应的 table_id。
配置路由设计简单,使用起来非常灵活,尤其是在扩充表的时候,只需要迁移指定的数据,然后修改路由表就可以了。
配置路由的缺点就是必须多查询一次,会影响整体性能;而且路由表本身如果太大(例如,几亿条数据),性能同样可能成为瓶颈,如果我们再次将路由表分库分表,则又面临一个死循环式的路由算法选择问题。
三、分库分表带来的问题
分库分表有效的缓解了大数据、高并发带来的性能和压力,也能突破网络IO、硬件资源、连接数的瓶颈,但同时也带来了一些问题。
3.1 事务一致性问题
由于分库分表把数据分布在不同库甚至不同服务器,不可避免会带来分布式事务问题,我们需要额外编程解决该问题。
3.2、跨节点join
在没有进行分库分表前,我们检索商品时可以通过以下SQL对店铺信息进行关联查询。但经过分库分表后,[商品信息]和[店铺信息]不在一个数据库或一个表中,甚至不在一台服务器上,无法通过sql语句进行关联查询,我们需要额外编程解决该问题。
3.3、跨节点分页、排序和聚合函数
跨节点多库进行查询时,limit分页、order by排序以及聚合函数等问题,就变得比较复杂了。需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序。例如,进行水平分库后的商品库,按ID倒序排序分页,取第一页:
以上流程是取第一页的数据,性能影响不大,但由于商品信息的分布在各数据库的数据可能是随机的,如果是取第N页,需要将所有节点前N页数据都取出来合并,再进行整体的排序,操作效率可想而知,所以请求页数越大,系统的性能也会越差。
在使用Max、Min、Sum、Count之类的函数进行计算的时候,与排序分页同理,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。
3.4、主键避重
在分库分表环境中,由于表中数据同时存在不同数据库中,主键值平时使用的自增长将无用武之地,某个分区数据库生成的ID无法保证全局唯一。因此需要单独设计全局主键,以避免跨库主键重复问题。由于分库分表之后,数据被分散在不同的服务器、数据库和表中。因此,对数据的操作也就无法通过常规方式完成,并且它还带来了一系列的问题。我们在开发过程中需要通过一些中间件解决这些问题,市面上有很多中间件可供我们选择,其中Sharding-JDBC和mycat较为流行。
四、使用分库分表中间件
站在巨人的肩膀上能省力很多,目前分库分表已经有一些较为成熟的开源解决方案: