关于“数据库优化”你知道多少?(下)

1、分表技术(垂直分割、水平分割、库表散列)

1.1 分表技术由来

 关系型数据库本身比较容易成为系统性能瓶颈,单机存储容量、连接数、处理能力等都很有限,数据库本身的“有状态性”导致了它并不像Web和应用服务器那么容易扩展。在互联网行业海量数据和高并发访问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding、分片)。同时,流行的分布式系统中间件(例如MongoDB、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小异的。

1.2 分布式全局唯一ID

 在很多中小项目中,我们往往直接使用数据库自增特性来生成主键ID,这样确实比较简单。而在分库分表的环境中,数据分布在不同的分片上,不能再借助数据库自增长特性直接生成,否则会造成不同分片上的数据表主键会重复。简单介绍下使用和了解过的几种ID生成算法。

  • Twitter的Snowflake(又名“雪花算法”)
  • UUID/GUID(一般应用程序和数据库均支持)
  • MongoDB ObjectID(类似UUID的方式)
  • Ticket Server(数据库生存方式,Flickr采用的就是这种方式)

1.3 分片字段该如何选择

 在开始分片之前,我们首先要确定分片字段(也可称为“片键”)。很多常见的例子和场景中是采用ID或者时间字段进行拆分。这也并不绝对的,我的建议是结合实际业务,通过对系统中执行的sql语句进行统计分析,选择出需要分片的那个表中最频繁被使用,或者最重要的字段来作为分片字段。

1.4 常见分片规则

 常见的分片策略有随机分片和连续分片这两种

 当需要使用分片字段进行范围查找时,连续分片可以快速定位分片进行高效查询,大多数情况下可以有效避免跨分片查询的问题。后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移。但是,连续分片也有可能存在数据热点的问题,就像图中按时间字段分片的例子,有些节点可能会被频繁查询压力较大,热数据节点就成为了整个集群的瓶颈。而有些节点可能存的是历史数据,很少需要被查询到。

 随机分片其实并不是随机的,也遵循一定规则。通常,我们会采用Hash取模的方式进行分片拆分,所以有些时候也被称为离散分片。随机分片的数据相对比较均匀,不容易出现热点和并发访问的瓶颈。但是,后期分片集群扩容起来需要迁移旧的数据。使用一致性Hash算法能够很大程度的避免这个问题,所以很多中间件的分片集群都会采用一致性Hash算法。离散分片也很容易面临跨分片查询的复杂问题。

1.5 跨分片的排序分页

 一般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。

1.6 ER分片

 在关系型数据库中,表之间往往存在一些关联的关系。如果我们可以先确定好关联关系,并将那些存在关联关系的表记录存放在同一个分片上,那么就能很好的避免跨分片join问题。在一对多关系的情况下,我们通常会选择按照数据较多的那一方进行拆分

2、 水平分割

 核心思想:把一个数据量大的表分成N个数据量较小的表,表的结构都一样,只是数据不一样。

一个简单易懂的例子:

在食堂买饭,只有一个窗口打饭,排队的人只能排在这个窗口(窗口A),能打到饭的效率很低(这里打饭就相当于每个用户对表执行的Select),排队的人哀声一遍;这时,食堂的大BOSS出现了,命令新开了3个窗口(窗口B、C、D),原本在窗口A排队的人都纷纷跑去窗口B、C、D排队了;很快,所有人都打到饭了。

例子2:通过ID(例如QQ号)直接登录

 表中的数据时,只需要把传过来的id除以3取模根据模可以找到对应的表,然后再去对应的表做查询操作

例子3:通过邮箱登录,通过邮箱对表分割

 本原理都是和Id差不多,就是要通过一个算法把md5字符串转成十进制的数,然后再取模。

水平分割通常在下面情况下使用:

  • 表数据量很大,分割后可以降低在查询时需要读的数据和索引的页数,同时也降低了索引的层数,加快了查询速度。
  • 表中的数据本来就有独立性,例如表中分别记录各个地区的数据或不同时期的数据,特别是有些数据常用,而另外一些数据不常用。
  • 需要把数据存放到多个介质上。
  • 需要把历史数据和当前的数据拆分开。

优点:

 降低在查询时需要读的数据和索引的页数,同时也降低了索引的层数,加快了查询速度。

缺点:

 水平分割会给应用增加复杂度,它通常在查询时需要多个表名,查询所有数据需要union操作。在许多数据库应用中,这种复杂性会超过它带来的优点,因为只要索引关键字不大,则在索引用于查询时,表中增加两到三倍数据量,查询时也就增加读一个索引层的磁盘次数。

3、垂直分割

什么是垂直分割呢?

 打个形象的比喻, 一个小公司通过短短几年发展变成了一个跨国大企业, 以前的部门架构明显不能满足现在的业务发展,CEO噼里啪啦的把公司分成了财务、人事部、生产部、销售部门…..,一下子成立了多个部门,各司其职。

 你垂直分割表(不破坏第三范式),把主码(主键)和一些列放到一个表,然后把主码(主键)和另外的一些列放到另一个表中。将原始表分成多个只包含较少列的表。如果一个表中某些列常用,而另外一些列不常用,则可以采用垂直分割。

优点:

  • 垂直分割可以使得行数据变小,一个数据块(Block)就能存放更多的数据,在查询时就会减少I/O次数(每次查询时读取的Block 就少)。
  • 垂直分割表可以达到最大化利用Cache的目的。

缺点:

  • 表垂直分割后,主码(主键)出现冗余,需要管理冗余列。
  • 会引起表连接JOIN操作(增加CPU开销)需要从业务上规避。

4、库表散列

 表散列与水平分割相似,但没有水平分割那样的明显分割界限,采用Hash算法把数据分散到各个分表中, 这样IO更加均衡。一般来说,我们会按照业务或者功能模块将数据库进行分离,不同的模块对应不同的数据库或者表,再按照一定的策略对某个页面或者功能进行更小的数据库散列,比如用户表按照用户ID进行表散列,散列128张表,则应就能够低成本的提升系统的性能并且有很好的扩展性。

5、我们的系统真的需要分库分表吗

 读完上面内容,不禁引起有些读者的思考,我们的系统是否需要分库分表吗?

 其实这点没有明确的判断标准,比较依赖实际业务情况和经验判断。依照笔者个人的经验,一般MySQL单表1000W左右的数据是没有问题的(前提是应用系统和数据库等层面设计和优化的比较好)。当然,除了考虑当前的数据量和性能情况时,作为架构师,我们需要提前考虑系统半年到一年左右的业务增长情况,对数据库服务器的QPS、连接数、容量等做合理评估和规划,并提前做好相应的准备工作。如果单机无法满足,且很难再从其他方面优化,那么说明是需要考虑分片的。这种情况可以先去掉数据库中自增ID,为分片和后面的数据迁移工作提前做准备。

 很多人觉得“分库分表”是宜早不宜迟,应该尽早进行,因为担心越往后公司业务发展越快、系统越来越复杂、系统重构和扩展越困难…这种话听起来是有那么一点道理,但我的观点恰好相反,对于关系型数据库来讲,我认为“能不分片就别分片”,除非是系统真正需要,因为数据库分片并非低成本或者免费的。

6、小结

 最后,有很多读者都想了解当前社区中有没有开源免费的分库分表解决方案,毕竟站在巨人的肩膀上能省力很多。当前主要有两类解决方案:

 基于应用程序层面的DDAL(分布式数据库访问层)

 比较典型的就是淘宝半开源的TDDL,当当网开源的Sharding-JDBC等。分 布式数据访问层无需硬件投入,技术能力较强的大公司通常会选择自研或

 参照开源框架进行二次开发和定制。对应用程序的侵入性一般较大,会增加 技术成本和复杂度。通常仅支持特定编程语言平台(Java平台的居多),或 者仅支持特定的数据库和特定数据访问框架技术(一般支持MySQL数据库
, JDBC、MyBatis、Hibernate等框架技术)。

 数据库中间件,比较典型的像mycat(在阿里开源的cobar基础上做了很多优化和改进,属于后起之秀,也支持很多新特性),基于Go语言实现kingSharding,比较老牌的Atlas(由360开源)等。这些中间件在互联网企业中大量被使用。另外,MySQL 5.x企业版中官方提供的Fabric组件也号称支持分片技术,不过国内使用的企业较少。