按照業務拆分的方式稱為垂直分片,又稱為縱向拆分,它的核心理念是專庫專用;水平分片又稱為橫向拆分,是通過某些欄位根據某種規則將數據分散至多個庫或表中。 ...
標簽:ShardingSphere5.分庫.分表;
一、簡介
分庫分表的設計和實現方式,在之前的內容中總結過很多,本文基於SpringBoot3
和ShardingSphere5
框架實現數據分庫分表的能力;
不得不提ShardingSphere5
文檔中描述的兩個基本概念:
垂直分片
按照業務拆分的方式稱為垂直分片,又稱為縱向拆分,它的核心理念是專庫專用。在拆分之前,一個資料庫由多個數據表構成,每個表對應著不同的業務。而拆分之後,則是按照業務將表進行歸類,分佈到不同的資料庫中,從而將壓力分散至不同的資料庫。
水平分片
水平分片又稱為橫向拆分。 相對於垂直分片,它不再將數據根據業務邏輯分類,而是通過某個欄位(或某幾個欄位),根據某種規則將數據分散至多個庫或表中,每個分片僅包含數據的一部分。
下麵從案例實踐中,看看ShardingSphere5
框架是如何實現分庫分表的原理;
二、工程搭建
1、工程結構
2、依賴管理
這裡只看兩個核心組件的依賴:shardingsphere-jdbc
組件是5.2.1
版本,mybatis
組件是3.5.13
版本,在依賴管理中還涉及MySQL和分頁等,並且需要添加很多排除配置,具體見源碼;
<!-- Mybatis組件 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- ShardingSphere分庫分表 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
三、配置詳解
1、配置文件
此處只展示分庫分表的相關配值,預設數據源使用db_master
庫,註意tb_order
庫表路由的策略和分片演算法的關聯關係,其他工程配置詳見源碼倉庫;
spring:
# 分庫分表配置
shardingsphere:
datasource:
# 預設數據源
sharding:
default-data-source-name: db_master
names: db_master,db_0,db_1
db_master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/shard_db
username: root
password: 123456
db_0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/shard_db_0
username: root
password: 123456
db_1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/shard_db_1
username: root
password: 123456
rules:
sharding:
tables:
# tb_order邏輯
tb_order:
actual-data-nodes: db_${0..1}.tb_order_${0..2}
# tb_order庫路由
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: database_inline
# tb_order表路由
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table_inline
sharding-algorithms:
# tb_order庫路由演算法
database_inline:
type: INLINE
props:
algorithm-expression: db_${order_id % 2}
# tb_order表路由演算法
table_inline:
type: INLINE
props:
algorithm-expression: tb_order_${order_id % 3}
props:
sql-show: true
sql-comment-parse-enabled: true
2、配置原理
在配置中需要管理三個數據源,shard_db
預設庫,在操作不涉及需要路由的表時預設使用該數據源,shard_db_0
和shard_db_1
是tb_order
邏輯表的路由庫;
邏輯表tb_order
整體使用兩個資料庫,每個庫建3張結構相同相同的表,在操作tb_order
數據時,會根據order_id
欄位值定位數據所屬的分片節點;
- 庫路由
db_${0..1}
採用db_${order_id%2}
的演算法; - 表路由
tb_order_${0..2}
採用tb_order_${order_id%3}
的演算法;
四、測試案例
1、主庫操作
基於Mybatis持久層框架,實現對shard_db
預設庫的數據操作,註意控制台的日誌列印,可以看到一系列解析邏輯以及庫表節點的定位,分頁查詢使用PageHelper組件即可;
public class MasterTest {
@Autowired
private BuyerMapper buyerMapper ;
@Autowired
private SellerMapper sellerMapper ;
@Test
public void testBuyerQuery (){
// 主鍵查詢
Buyer buyer = buyerMapper.selectByPrimaryKey(1) ;
System.out.println(buyer.getId()+";"+buyer.getBuyerName());
}
@Test
public void testBuyerInsert (){
// 新增數據
Buyer buyer = new Buyer() ;
buyer.setBuyerName("買家Three");
System.out.println(buyerMapper.insert(buyer));
}
@Test
public void testBuyerUpdate (){
// 更新數據
Buyer buyer = buyerMapper.selectByPrimaryKey(3) ;
if (buyer != null){
buyer.setBuyerName("Three買家");
System.out.println(buyerMapper.updateByPrimaryKey(buyer));
}
}
@Test
public void testSellerPage (){
// 1、設置分頁和查詢條件
PageHelper.startPage(2,2) ;
SellerExample sellerExample = new SellerExample() ;
sellerExample.setOrderByClause("id asc");
// 2、查詢數據
List<Seller> sellerList = sellerMapper.selectByExample(sellerExample) ;
// 3、構建分頁實體對象
PageInfo<Seller> pageInfo = new PageInfo<>(sellerList) ;
System.out.println(pageInfo);
}
}
2、分庫操作
在對tb_order
表執行增刪改查時,會根據order_id
的欄位值計算庫表的路由節點,註意分頁時會查詢所有的分庫和分表,然後彙總查詢的結果;
public class ShardTest {
@Autowired
private OrderMapper orderMapper ;
/**
* 寫入100條數據
*/
@Test
public void testOrderInsert (){
for (int i=1 ; i<= 100 ; i++){
Order order = new Order(i,i%3+1,i%3+1) ;
// orderMapper.insert(order) ;
}
}
@Test
public void testOrderQuery (){
Order order = orderMapper.selectByPrimaryKey(5) ;
System.out.println(order);
}
@Test
public void testOrderUpdate (){
Order order = orderMapper.selectByPrimaryKey(100) ;
if (order != null){
// 原數據:買家和賣家ID都是2
order.setBuyerId(1);
order.setSellerId(3);
orderMapper.updateByPrimaryKey(order) ;
}
}
@Test
public void testOrderPage (){
// 1、設置分頁和查詢條件
PageHelper.startPage(1,10) ;
OrderExample orderExample = new OrderExample() ;
orderExample.createCriteria().andBuyerIdEqualTo(2).andSellerIdEqualTo(2);
orderExample.setOrderByClause("order_id desc");
// 2、查詢數據
List<Order> orderList = orderMapper.selectByExample(orderExample) ;
// 3、構建分頁實體對象
PageInfo<Order> pageInfo = new PageInfo<>(orderList) ;
System.out.println(pageInfo);
}
}
3、綜合查詢
編寫一個訂單詳情查詢介面,同時使用三個庫構建數據結構;如果是基於列表數據的檢索,比較常規做法的是構建ES索引結構,如果沒有搜索的需求,可以在訂單表分頁查詢後去拼接其他結構;
@RestController
public class OrderController {
@Resource
private BuyerMapper buyerMapper ;
@Resource
private SellerMapper sellerMapper ;
@Resource
private OrderMapper orderMapper ;
/**
* 查詢訂單詳情
*/
@GetMapping("/order/info/{orderId}")
public Map<String,Object> orderInfo (@PathVariable Integer orderId){
Map<String,Object> orderMap = new HashMap<>() ;
Order order = orderMapper.selectByPrimaryKey(orderId) ;
if (order != null){
orderMap.put("order",order) ;
orderMap.put("buyer",buyerMapper.selectByPrimaryKey(order.getBuyerId())) ;
orderMap.put("seller",sellerMapper.selectByPrimaryKey(order.getSellerId())) ;
}
return orderMap ;
}
}
查看SQL語句
db_master ::: select id, buyer_name from tb_buyer where id = ? ::: [1]
db_master ::: select id, seller_name from tb_seller where id = ? ::: [3]
db_0 ::: select order_id, seller_id, buyer_id from tb_order_1 where order_id = ? ::: [100]
五、參考源碼
文檔倉庫:
https://gitee.com/cicadasmile/butte-java-note
源碼倉庫:
https://gitee.com/cicadasmile/butte-spring-parent
Gitee主頁: https://gitee.com/cicadasmile/butte-java-note