随着互联网的发展,用户规模不断扩大,Mysql单库单表的性能问题也会随之暴露。
下面几个因素,会直接影响数据库性能:
数据量
虽然MySQL单表可以存储10亿级的数据,但如果数据库硬件不是很好,或者遇到数据表结构设计优化不到位,当单表数据量达到几千万上亿时,性能就会有折扣。
磁盘io
磁盘IO经常会成为系统的一个瓶颈,特别是对于运行数据库的系统而言。数据从磁盘读取到内存,再到CPU缓存和寄存器,然后进行处理,最后写回磁盘,中间要经过很多的过程。
因此在并发压力下,所有的请求都访问同一个节点,肯定会对磁盘IO造成非常大的影响。
数据库连接
数据库连接是非常稀少的资源,如果一个库里既有用户、商品、订单相关的数据,当海量用户同时操作时,数据库连接就很可能成为瓶颈。
为了提升线上服务的处理性能,我们就有必要考虑对Mysql进行分库分表。
常见方案:
垂直分表
即基于列字段拆分的“大表拆多个小表”。将同一个表中的大量不常用的、数据长度较长的字段拆分到“扩展表”。
扩展表可以有很多种形式,单表形式、宽表形式、kv表形式,等等。
垂直分库
同一个库中,太多不同业务维度的数据表。比如一个库里同时包括用户、商品、订单、费用表。 当一个电商网站对外提供服务时,用户的行为可能会对这些表同时进行操作,单库的磁盘空间、磁盘io、内存就会产生较大的影响。
如上图,将其拆分到多个服务器上,以释放单机资源。
水平分库分表
将单张表的数据切分到多个服务器,每个服务器具有相应的库与表,只是表中数据集合不同。
这样能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等瓶颈。
常见的几种水平拆分策略:
时间分片
按月分片,按季度分片等等,可以做到冷热数据。分页与合并排序,可以利用UNION ALL解决,当然前提是子表的结果集数据量不能太大。
Hash取模
假设有订单表order,将其分为4个表,order0,order1,order2,order3,路由规则 = orderId % 4;当orderId=3时,对应的是order3。
范围分片
从1-10000一个表,10001-20000一个表。
地理位置分片
北京一个表,上海一个表,南京一个表。
分库分表后引入的问题
分布式事务问题
如果我们做了垂直分库或者水平分库以后,就必然会涉及到跨库执行SQL的问题,这样就引发了互联网界的老大难问题-"分布式事务"。那要如何解决这个问题呢?
- 使用分布式事务中间件
- 使用MySQL自带的针对跨库的事务一致性方案(XA)。
- 能否避免掉跨库操作(比如将用户和商品放在同一个库中)。
跨库join的问题
分库分表后表之间的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表,结果原本一次查询能够完成的业务,可能需要多次查询才能完成。
粗略的解决方法:全局表:基础数据,所有库都拷贝一份。字段冗余:这样有些字段就不用join去查询了。系统层组装:分别查询出所有,然后组装起来,较复杂。
结果集合并、排序的问题
因为我们是将数据分散存储到不同的库、表里的,当我们查询指定数据列表时,数据来源于不同的子库或者子表,就必然会引发结果集合并、排序的问题。
如果每次查询都需要排序、合并等操作,性能肯定会受非常大的影响。
怎么分,能同时解决上述问题?
分布式ID, 水平分库, 垂直分表
以用户下单的订单库的场景举例:
分布式订单ID
利用类似snowflake算法生成一个64bit位的分布式id,作为订单唯一id。如下图所示:
这样的订单id可以直接定位到所在数据库。
go语言版实现:github.com/HaroldHoo/i…
用户ID取模,水平分库
用户下单之前,将用户id取模的值,作为上述「分布式订单ID」的一部分,生成分布式订单ID后创建订单。
这样,同一个用户所产生的订单,将始终在同一个数据库中,可以满足同一用户在订单维度的结果集合并、排序、分页。
按不同业务维度,垂直分表
用类似上一步的方式,可以将其他业务维度的表,比如费用详情表、用户表、评价表,都放在同一个库中。
通过以上步骤,完成分库分表的同时,跨库事务和跨库join问题,也将迎刃而解。
欢迎关注公众号「架构观察」,热爱技术,喜欢阅读,热衷于分享和总结,希望能把每一篇好文章分享给成长道路上的你。公号回复666,有你想要的资源。