一、分库分表的触发条件
在mysql数据库的使用过程中,当数据量增长到一定规模时,单库单表的架构会面临性能瓶颈,此时就需要考虑分库分表。以下是常见的触发场景:
1.1 数据量阈值
- 单表数据量达到1000万-2000万行时,查询性能会明显下降。这是因为mysql的b+树索引在数据量过大时,树的高度增加,会导致磁盘io次数增多,查询效率降低。
- 例如,一个电商平台的订单表,随着业务的增长,每月新增订单量达到数百万,经过一年多的积累,数据量突破1500万,此时简单的查询如“查询用户近三个月的订单”响应时间从原来的几百毫秒增加到几秒,严重影响用户体验。
1.2 并发压力
当数据库的并发连接数过高,超过单库的处理能力时,会出现连接超时、锁等待等问题。
- 比如一个社交应用的消息表,在高峰期每秒有数千次的读写操作,单库无法承受这样的并发压力,导致消息发送延迟、读取失败等情况。
二、分库分表的核心技术模块
2.1 水平分表
水平分表是将一个表中的数据按照某种规则(如范围、哈希)拆分成多个结构相同的子表,每个子表只包含一部分数据。
2.1.1 技术原理
- 范围分片:按照数据的某个字段(如时间、id范围)进行分片。例如,订单表按照月份分片,每个月的数据存放在一个子表中。
- 哈希分片:对数据的某个字段进行哈希计算,根据哈希结果将数据分配到不同的子表中。比如,根据用户id进行哈希,将不同用户的订单分配到不同的子表。
2.1.2 案例与代码实现
案例:一个电商平台的订单表orders
,包含字段order_id
(订单id)、user_id
(用户id)、order_time
(下单时间)等,数据量达到2000万,需要进行水平分表。采用按order_id
范围分片,每500万订单id为一个区间,分为4个子表orders_1
、orders_2
、orders_3
、orders_4
。
代码实现:
-- 创建分表 create table orders_1 ( order_id bigint not null primary key, user_id bigint not null, order_time datetime not null, -- 其他字段 ) engine=innodb default charset=utf8mb4 where order_id between 1 and 5000000; create table orders_2 ( order_id bigint not null primary key, user_id bigint not null, order_time datetime not null, -- 其他字段 ) engine=innodb default charset=utf8mb4 where order_id between 5000001 and 10000000; create table orders_3 ( order_id bigint not null primary key, user_id bigint not null, order_time datetime not null, -- 其他字段 ) engine=innodb default charset=utf8mb4 where order_id between 10000001 and 15000000; create table orders_4 ( order_id bigint not null primary key, user_id bigint not null, order_time datetime not null, -- 其他字段 ) engine=innodb default charset=utf8mb4 where order_id between 15000001 and 20000000; -- 创建视图,方便查询 create view orders as select * from orders_1 union all select * from orders_2 union all select * from orders_3 union all select * from orders_4;
2.2 垂直分表
垂直分表是将一个表中字段较多的表,按照字段的热点程度、访问频率等,拆分成多个包含部分字段的子表。
2.2.1 技术原理
- 将经常被查询的热点字段放在一个子表中,将不常被查询的冷字段放在另一个子表中。这样可以减少每次查询时读取的数据量,提高查询效率。
- 例如,用户表中,用户的基本信息(如用户名、手机号)经常被查询,而用户的详细信息(如家庭住址、个人简介)不常被查询,可以将其拆分成两个子表。
2.2.2 案例与代码实现
案例:用户表user
包含字段user_id
、username
、phone
、address
、introduction
等,其中username
、phone
经常被查询,address
、introduction
不常被查询,进行垂直分表。
代码实现:
-- 创建用户基本信息表(热点字段) create table user_base ( user_id bigint not null primary key, username varchar(50) not null, phone varchar(20) not null ) engine=innodb default charset=utf8mb4; -- 创建用户详细信息表(冷字段) create table user_detail ( user_id bigint not null primary key, address varchar(200), introduction text, foreign key (user_id) references user_base(user_id) ) engine=innodb default charset=utf8mb4;
2.3 分库
分库是将多个表按照一定的规则拆分到不同的数据库中,以降低单库的压力。
2.3.1 技术原理
- 可以按照业务模块进行分库,将不同业务模块的表放在不同的数据库中。例如,电商平台可以将用户相关的表放在用户库,订单相关的表放在订单库。
- 也可以结合分表进行分库,将分表后的子表分布到不同的数据库中。
2.3.2 案例与代码实现
案例:一个大型电商平台,包含用户模块、商品模块、订单模块,将这三个模块的表分别放在user_db
、product_db
、order_db
三个数据库中。
代码实现:
- 在不同的数据库实例中分别创建对应的表,这里以用户库和订单库为例:
-- 在user_db数据库中创建用户相关表 use user_db; create table user_base ( user_id bigint not null primary key, username varchar(50) not null, phone varchar(20) not null ) engine=innodb default charset=utf8mb4; -- 在order_db数据库中创建订单相关表 use order_db; create table orders_1 ( order_id bigint not null primary key, user_id bigint not null, order_time datetime not null -- 其他字段 ) engine=innodb default charset=utf8mb4;
三、分库分表带来的新问题
3.1 分布式事务
分库分表后,一个业务操作可能涉及多个数据库或多个表,此时保证事务的一致性变得复杂。
3.1.1 问题说明
在单库单表中,mysql的acid特性可以保证事务的一致性。但在分布式环境下,多个数据库之间无法直接使用本地事务,可能出现部分操作成功、部分操作失败的情况。
3.1.2 案例
用户下单操作,需要在订单库中创建订单记录,同时在库存库中减少商品库存。如果订单创建成功,但库存减少失败,就会出现数据不一致。
3.2 跨库查询
分库分表后,查询可能需要涉及多个数据库或多个表,增加了查询的复杂度。
3.2.1 问题说明
例如,查询某个用户在多个月份的订单,由于订单表按月份分表且可能分布在不同的库中,需要同时查询多个库和表,然后合并结果。
3.2.2 案例
查询用户user_id=100
在2023年1月和2月的订单,需要分别查询order_db1
中的orders_202301
表和order_db2
中的orders_202302
表,然后将结果合并。
3.3 数据迁移与扩容
随着业务的发展,可能需要对分库分表的方案进行调整,如增加分表数量、调整分片规则等,这会涉及到数据的迁移和扩容。
3.3.1 问题说明
数据迁移过程中需要保证数据的一致性和完整性,同时要尽量减少对业务的影响。扩容时需要考虑新的分片规则如何与原有规则兼容。
3.3.2 案例
原来订单表按照order_id
范围分表,每500万一个表,现在由于业务增长,需要将每个分表的范围调整为250万,需要将原有的orders_1
表(1-500万)拆分成orders_1
(1-250万)和orders_5
(251-500万),并迁移数据。
四、分库分表的解决方案
4.1 中间件方案
使用专门的分库分表中间件,如sharding-jdbc、mycat等,这些中间件可以帮助开发者透明地实现分库分表,减少手动处理的复杂度。
4.1.1 sharding-jdbc
- 原理:sharding-jdbc作为jdbc的增强版,通过对jdbc接口的封装,实现了分库分表的功能。它可以解析sql语句,根据分片规则路由到对应的数据库和表,并将结果合并返回。
- 案例:使用sharding-jdbc实现订单表的水平分表,按照
order_id
取模分片。
代码实现(spring boot整合sharding-jdbc):
spring: shardingsphere: datasource: names: db0,db1 db0: type: com.zaxxer.hikari.hikaridatasource driver-class-name: com.mysql.cj.jdbc.driver jdbc-url: jdbc:mysql://localhost:3306/order_db0 username: root password: root db1: type: com.zaxxer.hikari.hikaridatasource driver-class-name: com.mysql.cj.jdbc.driver jdbc-url: jdbc:mysql://localhost:3306/order_db1 username: root password: root rules: sharding: tables: orders: actual-data-nodes: db${0..1}.orders_${0..1} database-strategy: standard: sharding-column: order_id sharding-algorithm-name: order_db_inline table-strategy: standard: sharding-column: order_id sharding-algorithm-name: order_table_inline sharding-algorithms: order_db_inline: type: inline props: algorithm-expression: db${order_id % 2} order_table_inline: type: inline props: algorithm-expression: orders_${order_id % 2} props: sql-show: true
4.2 分布式事务解决方案
4.2.1 两阶段提交(2pc)
- 原理:分为准备阶段和提交阶段。准备阶段,协调者向所有参与者发送准备请求,参与者执行事务操作但不提交,并反馈是否可以提交;提交阶段,如果所有参与者都反馈可以提交,协调者发送提交请求,否则发送回滚请求。
- 缺点:性能较差,协调者故障可能导致参与者处于阻塞状态。
4.2.2 最终一致性方案(如tcc、saga)
- tcc(try-confirm-cancel):将一个事务拆分为try、confirm、cancel三个操作。try阶段尝试执行事务,预留资源;confirm阶段确认执行事务;cancel阶段取消事务,释放资源。
- saga:将一个长事务拆分为多个短事务,每个短事务都有对应的补偿事务,当某个短事务失败时,执行前面所有成功的短事务的补偿事务,以保证数据的最终一致性。
tcc案例代码(伪代码):
// 订单服务 public interface ordertccservice { // try阶段:创建订单,预留库存 boolean trycreateorder(orderdto orderdto); // confirm阶段:确认创建订单 boolean confirmcreateorder(orderdto orderdto); // cancel阶段:取消创建订单,释放库存 boolean cancelcreateorder(orderdto orderdto); } // 库存服务 public interface inventorytccservice { // try阶段:扣减库存预留 boolean trydeductinventory(inventorydto inventorydto); // confirm阶段:确认扣减库存 boolean confirmdeductinventory(inventorydto inventorydto); // cancel阶段:取消扣减库存,恢复库存 boolean canceldeductinventory(inventorydto inventorydto); }
五、分库分表的最佳实践
5.1 合理选择分片策略
- 根据业务特点选择合适的分片策略,如订单表可以按照时间范围分片,方便查询历史数据;用户表可以按照用户id哈希分片,使数据分布均匀。
- 避免过度分片,分片数量过多会增加管理复杂度和跨库查询的开销。
5.2 做好数据备份与恢复
分库分表后的数据分布在多个库和表中,需要制定完善的数据备份策略,定期备份数据,并确保备份数据可以正常恢复。
5.3 监控与调优
- 对分库分表后的数据库进行实时监控,包括各库表的性能指标(如查询响应时间、吞吐量、连接数等)、数据增长情况等。
- 根据监控结果进行调优,如调整分片规则、优化sql语句、增加硬件资源等。
5.4 考虑未来扩展性
在设计分库分表方案时,要考虑未来业务的增长,预留一定的扩展空间,使方案能够方便地进行扩容和调整。例如,采用可扩展的分片规则,当数据量增长到一定程度时,可以方便地增加新的分库分表。
到此这篇关于mysql分库分表的实践与挑战的文章就介绍到这了,更多相关mysql分库分表内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论