当单表行数超过500W行或者单表数据容量超过2G时,就会对查询性能产生较大影响,这个时候建议对表进行优化。
其实500W数据只是一个折中的值,具体的数据量和数据库服务器配置以及mysql配置有关,因为Mysql为了提升性能,会把表的索引装载到内存,innodb_buffer_pool_size 足够的情况下,mysql能把全部数据加载进内存,查询不会有问题。
但是,当单表数据库到达某个量级的上限时,导致内存无法存储其索引,使得之后的 SQL 查询会产生磁盘 IO,从而导致性能下降。当然,这个还有具体的表结构的设计有关,最终导致的问题都是内存限制,这里,增加硬件配置,可能会带来立竿见影的性能提升。innodb_buffer_pool_size 包含数据缓存、索引缓存等
Mysql常见的优化手段
增加索引,索引是直观也是最快速优化检索效率的方式。
基于Sql语句的优化,比如最左匹配原则,用索引字段查询、降低sql语句的复杂度、尽量使用聚集索引如若要使用非聚集索引,返回列里不要出现多余的字段减少回表查询等。
表的合理设计,比如符合三范式、或者为了一定的效率破坏三范式设计等
数据库参数优化,比如并发连接数、数据刷盘策略、调整缓存大小
数据库服务器硬件升级
mysql主从复制方案,实现读写分离
这些常见的优化手段,在数据量较小的情况下效果非常好,但是数据量到达一定瓶颈时,常规的优化手段已经解决不了实际问题,那怎么办呢?
对于大数据表的优化最直观的方式就是减少单表数据量,所以常见的解决方案是:
分库分表,大表拆小表:冷热数据分离,所谓的冷热数据,其实就是根据访问频次来划分的,访问频次较多的数据是热数据,访问频次少的数据是冷数据。冷热数据分离就是把这两类数据分离到不同的表中,从而减少热数据表的大小。
一些网站查询订单或者交易记录,默认只允许查询1到3个月,3个月之前的数据,基本上大家都很少关心,访问频次较少,所以可以把3个月之前的数据保存到冷库中。
历史数据归档:简单来说就是把时间比较久远的数据分离出来存档,保证实时库的数据的有效生命周期。
这些解决方案都是属于偏业务类的方案,并不完全是技术上的方案,所以在实施的时候,需要根据业务的特性来选择合适的方式。
详解分库分表
分库分表是非常常见针对单个数据表数据量过大的优化方式,它的核心思想是把一个大的数据表拆分成多个小的数据表,这个过程也叫(数据分片),它的本质其实有点类似于传统数据库中的分区表,比如mysql和oracle都支持分区表机制。
分库分表是一种水平扩展手段,每个分片上包含原来总的数据集的一个子集。这种分而治之的思想在技术中很常见,比如多CPU、分布式架构、分布式缓存等等,像redis cluster集群,slot槽的分配就是一种数据分片的思想。
垂直拆分
垂直拆分有两种,一种是单库的垂直拆分,另一种是多个数据库的垂直拆分。
单库垂直分表
单个表的字段数量建议控制在20~50个之间,之所以建议做这个限制,是因为如果字段加上数据累计的长度超过一个阈值后,数据就不是存储在一个页上,就会产生分页的问题,而这个问题会导致查询性能下降。
所以如果当某些业务表的字段过多时,我们一般会拆去垂直拆分的方式,把一个表的字段拆分成多个表,如图所示,把一个订单表垂直拆分成一个订单主表和一个订单明细表。
在InnoDB引擎当中,单表字段最大限制为1017个:https://dev.mysql.com/doc/mysql-reslimits-excerpt/8.0/en/column-count-limit.html
- Storage engines may impose additional restrictions that limit table column count. For example, InnoDB has a limit of 1017 columns per table. See InnoDB Limits. For information about other storage engines, see Alternative Storage Engines.
多库垂直分表
多库垂直拆分实际上就是把存在于一个库中的多个表,按照一定的纬度拆分到多个库中,如图6-3所示。这种拆分方式在微服务架构中也是很常见,基本上会按照业务纬度拆分数据库,同样该纬度也会影响到微服务的拆分,基本上服务和数据库是独立的。
多库垂直拆分最大的好处就是实现了业务数据的隔离。其次就是缓解了请求的压力,原本所有的表在一个库的时候,所有请求都会打到一个数据库服务器上,通过数据库的拆分,可以分摊掉请求,在这个层面上提升了数据库的吞吐能力。
水平拆分
垂直拆分的方式并没有解决单表数据量过大的问题,所以我们还需要通过水平拆分的方式把大表数据做数据分片。
水平切分也可以分成两种,一种是单库的,一种是多库的。
单库水平分表
如图所示,表示把一张有10000条数据的用户表,按照某种规则拆分成了4张表,每张表的数据量是2500条。
银行的交易流水表和证券公司的资金股份流水表
所有进出的交易都需要登记这张表,因为绝大部分时候客户都是查询当天的交易和一个月以内的交易数据,所以我们根据使用频率把这张表拆分成三张表:
当天表:只存储当天的数据
当月表:我们在夜间运行一个定时任务,前一天的数据,全部迁移到当月表。用的是insert into select,然后delete。
历史表:同样是通过定时任务,把登记时间超过30天的数据,迁移到history历史表(历史表的数据非常大,我们按照月度,每个月建立分区)。
费用表:
消费金融公司跟线下商户合作,给客户办理了贷款,消费金融公司要给商户返费用,或者叫提成,每天都会产生很多的费用的数据。为了方便管理,我们每个月建立一张费用表,例如fee_detail_201901……fee_detail_201912。
但是注意,跟分区一样,这种方式虽然可以一定程度解决单表查询性能的问题,但是并不能解决单机存储瓶颈的问题。
多库水平分表
多库水平分表,其实有点类似于分库分表的综合实现方案,从分表来说是减少了单表的数据量,从分库层面来说,降低了单个数据库访问的性能瓶颈,如图所示。
常见的水平分表策略
分库更多的是关注业务的耦合度,也就是每个库应该放那些表,是由业务耦合度来决定的,这个在前期做领域建模的时候都会先考虑好,所以问题不大,只是分库之后带来的其他问题,我们在后续内容中来分析。
而分表这块,需要考虑的问题会更多一些,也就是我们应该根据什么样的策略来水平分表?这里就需要涉及到分表策略了,下面简单介绍几种最常见的分片策略。
哈希取模分片
哈希分片其实就是通过其中的某一个字段进行hash计算得到一个哈希值,然后通过取模运算确定数据应该放在那个区,如图所示,这种方式虽然非常适合随机读写的场景,它能够很好的将一个大表的数据随机分散到多个表当中。
hash取模的问题
hash取模运算有个比较严重的问题,假设根据当前数据表的量以及增长情况,我们把一个大表拆分成了4个小表,看起来满足目前的需求,但是经过一段时间的运行后,发现四个表不够,需要再增加4个表来存储,这种情况下,就需要对原来的数据进行整体迁移,这个过程非常麻烦。一般为了减少这种方式带来的数据迁移的影响,我们会采用一致性hash算法。
一致性hash算法
一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32^-1(即哈希值是一个32位无符号整形),什么意思呢?
通过0-2^32^-1的数字组成一个虚拟的圆环,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^32^-1,也就是说0点左侧的第一个点代表2^32^-1。我们把这个由2的32次方个点组成的圆环称为hash环。
假设现在有四个表,table_1、table_2、table_3、table_4,在一致性hash算法中,取模运算不是直接对这四个表来完成,而是对2^32^来实现。
hash(table编号)%2^32^
通过上述公式算出的结果一定是一个0到2^32^-1之间的一个整数,然后在这个数对应的位置标注目标表,如图所示,四个表通过hash取模之后分别落在hash环的某个位置上。
到目前为止,我们已经把目标表与hash环联系在了一起,那么接下来我们需要把一条数据保存到某个目标表中,怎么做呢?如图所示,当添加一条数据时,同样通过hash和hash环取模运算得到一个目标值,然后根据目标值所在的hash环的位置顺时针查找最近的一个目标表,把数据存储到这个目标表中即可。
hash运算不是直接面向目标表,而是面向hash环,这样的好处就是当需要删除某张表或者增加表的时候,对于整个数据变化的影响是局部的,而不是全局。
举个例子,假设我们发现需要增加一张表table_04,如图所示,增加一个表,并不会对其他四个已经产生了数据的表造成影响,原来已经分片的数据完全不需要做任何改动。
如果需要删除一个节点,同样只会影响删除节点本身的数据,前后表的数据完全不受影响。
hash环偏斜
上述设计有一个问题,理论情况下我们目标表是能够均衡的分布在整个hash环中,但实际情况有可能是图所示的样子。也就是产生了hash环偏斜的现象,这种现象导致的问题就是大量的数据都会保存到同一个表中,倒是数据分配极度不均匀。
为了解决这个问题,必须要保证目标节点要均匀的分布在整个hash环中,但是真实的节点就只有4个,如何均匀分布呢?最简单的方法就是,把这四个节点分别复制一份出来分散到这个hash环中,这个复制出来的节点叫虚拟节点,根据实际需要可以虚拟出多个节点出来,如图所示。
按照范围分片
时间范围,比如我们按照数据创建时间,按照每一个月保存一个表。基于时间划分还可以用来做冷热数据分离,越早的数据访问频次越少。
区域范围,区域一般指的是地理位置,比如一个表里面存储了来自全国各地的数据,如果数据量较大的情况下,可以按照地域来划分多个表。
数据范围,比如根据某个字段的数据区间来进行划分。
如图所示:表示按照数据范围进行拆分。
范围分片最终要的是选择一个合适的分片键,这个是否合适来自于业务需求,比如之前有个学员是在做智能家居的,他们卖的是硬件设备,这些设备会采集数据上报到服务器上,当来自全国范围的数据统一保存在一个表中后,数据量达到了亿级别,所以这种场景比较适合按照城市和地域来拆分。