1. 为什么要进行分库分表
大型应用系统需要处理大量用户的请求,比如微信,美团,淘宝等每天都会产生海量的数据,我们知道当数据量或者请求数达到一定数量之后,数据库就会产生性能瓶颈,而为了缓解数据量的压力,比较普遍的方案一方面是使用NoSQL,而另一方面就是分库分表。
数据库分库分表是一种常用的数据库性能优化技术,它可以在处理大规模数据时提高数据库的性能和可扩展性。分库分表将一个大型数据库拆分成多个小型数据库(分库),每个数据库再将表拆分成多个子表(分表)。 主要是解决如下问题:
- 数据量过大
- 高并发访问
- 数据局部性
- 容量扩展
这张图带你解决数据库压力问题:
2. 什么是分库分表
分库:从单个数据库拆分成多个数据库的过程,将数据散落在多个数据库中。
分表:从单张表拆分成多张表的过程,将数据散落在多张表内
3. 怎么选择分库分表策略
切分方案 | 解决的问题 |
只分库不分表 | 数据库读写QPS过高,数据库连接数不足 |
只分表不分库 | 解决单个表数据被分散,查询是B+Tree(MySQL)的高度比较低,减少磁盘IO,提升效率 |
分库分表 | 连接数不足+数据量过大引起的存储性能瓶颈 |
4. 分库分表的实现方案
当我们使用分库分表时,都在物理空间的拆分,主要有两种拆分模式,都可以应用到分库或分表中:
「垂直拆分」
- 垂直拆分又称为纵向拆分,应用时有垂直分库和垂直分表两种方式,主要解决表过多或者是表字段过多问题,一般谈到的垂直拆分主要指的是垂直分库。
- 垂直分库:是将不同的表分离到不同的库中。垂直分库本质是专库专用,指按照业务将表进行分类,分布到不同的数据库中,每个库可以放在不同的服务器上。优点:专库专用,业务层面解耦 能够针对不同业务的数据进行分级管理、维护、监控、扩展 在一定程度上提升了IO、数据库连接数、降低单机硬件资源的瓶颈;缺点:事务一致性的问题 多表连接查询困难。
- 垂直分表:垂直分表本质是将一个表按照字段分成多表,每个表存储其中一部分字段。垂直分表拆分原则:将热点字段和不常用的字段区分,放在不同的表中 将text,blob等大字段拆分出来放在附表中 将组合查询的列放在一张表中。优点;减少锁竞争,查询不同字段数据互不影响 可实现冷热分离的数据表设计 可以使得行数据变小,一个数据页能存放更多的数据,最大限度利用数据页缓存,减少查询的 I/O 次 数。缺点:事务一致性的问题 多表连接查询困难 无法解决单表数据量过大。
「水平拆分」
- 水平拆分又称为横向拆分,应用时有水平分库和水平分表两种方式,解决表中记录过多,缓解单机单库的性能瓶颈和压力问题。一般谈到的水平拆分主要指的是水平分库。
- 水平分库:水平分库的本质也是分表,是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。优点:「解决单个库高并发的性能瓶颈」切分的表的结构相同,应用层改造较少,只需要增加路由规则即可提高了系统的稳定性和负载能力。缺点:分片事务的一致性难以解决数据扩容的难度和维护量极大。
- 水平分表:水平分表的本质是数据分片,将不同的数据按照一定的规则( hash取模/range范围)将数据存储在不同的表中,以此减少单表的数据量,提高查询效率。优点:解决单表数据量大,查询性能下降的问题」可实现多表连接查询。缺点:引发排序、分页、函数计算等问题」数据扩容的难度和维护量极大。
5. 分表的缺点
分库分表有效的缓解了大数据、高并发带来的性能和压力,也能突破网络IO、硬件资源、连接数的瓶颈,但同时也带来了一些问题。
5.1、事务一致性问题
由于分库分表把数据分布在不同库甚至不同服务器,不可避免会带来分布式事务问题,我们需要额外编程解决该问题。
5.2、跨节点join
在没有进行分库分表前,我们检索商品时可以通过以下SQL对店铺信息进行关联查询:
SELECT p.*,s.[店铺名称],s.[信誉]
FROM [商品信息] p
LEFT JOIN [店铺信息] s ON p.id = s.[所属店铺]
WHERE...ORDER BY...LIMIT...
SELECT p.*,s.[店铺名称],s.[信誉] FROM [商品信息] p LEFT JOIN [店铺信息] s ON p.id = s.[所属店铺] WHERE...ORDER BY...LIMIT...
但经过分库分表后,[商品信息]和[店铺信息]不在一个数据库或一个表中,甚至不在一台服务器上,无法通过sql语句进行关联查询,我们需要额外编程解决该问题。
5.3、跨节点分页、排序和聚合函数
跨节点多库进行查询时,limit分页、order by排序以及聚合函数等问题,就变得比较复杂了。需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序。例如,进行水平分库后的商品库,按ID倒序排序分页,取第一页:
以上流程是取第一页的数据,性能影响不大,但由于商品信息的分布在各数据库的数据可能是随机的,如果是取第N页,需要将所有节点前N页数据都取出来合并,再进行整体的排序,操作效率可想而知,所以请求页数越大,系统的性能也会越差。
在使用Max、Min、Sum、Count之类的函数进行计算的时候,与排序分页同理,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。
5.4、分布式ID问题
在分库分表环境中,由于表中数据同时存在不同数据库中,主键值平时使用的自增长将无用武之地,某个分区数据库生成的ID无法保证全局唯一。因此需要单独设计全局主键,以避免跨库主键重复问题。有一些常见的主键生成策略:
- UUID
- 基于数据库自增单独维护一张 ID表
- 号段模式
- Redis
- 雪花算法(Snowflake)
- 美团Leaf
- 滴滴Tinyid
由于分库分表之后,数据被分散在不同的服务器、数据库和表中。因此,对数据的操作也就无法通过常规方式完成,并且它还带来了一系列的问题。我们在开发过程中需要通过一些中间件解决这些问题,市面上有很多中间件可供我们选择,其中Sharding-JDBC和mycat较为流行。
ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
- 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC;
- 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
- 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。
6、小结
本文从数据库实战场景中遇到的问题,给出了分库分表的定义、方案的选择,各种方案优点和缺点,以及业界的通用实现,希望大家在各自的业务场景中充分评估自己的核心问题,选择合适的优化方案。