一、爲什麼要分庫分表
1、高併發:數據庫單實例扛不住高併發,需多實例承受
2、數據量大:單機磁盤容量有限,數據庫數據量大時撐滿磁盤
3、sql執行速度:單表數據過大,sql執行速度極慢
二、有哪些技術支持
1、技術
- sharding-jdbc(sharding-sphere):噹噹開源的,client層方案,支持分庫分表、讀寫分離、分佈式 id 生成、柔性事務(最大努力送達型事務、TCC 事務)
- mycat:基於 cobar 改造的,proxy 層方案
- TDDL:淘寶團隊開發的,client 層方案。支持基本的 crud 語法和讀寫分離,但不支持 join、多表查詢等語法
- cobar:阿里 b2b 團隊開發和開源的,proxy 層方案,就是介於應用服務器和數據庫服務器之間。應用程序通過 JDBC 驅動訪問 cobar 集羣,cobar 根據 SQL 和分庫規則對 SQL 做分解,然後分發到 MySQL 集羣不同的數據庫實例上執行,不支持讀寫分離、存儲過程、跨庫 join 和分頁等操作。
- atlas:360 開源的,proxy 層方案
2、選型
sharding-jdbc和mycat是現在最火的,另外三種基本很少人用,然後sharding-jdbc呢不需要部署一套中間件,比較適合中小型企業,而mycat需要部署中間件,需要額外的維護但對項目透明不需要每個項目依賴,適合大型公司
三、如何進行拆分
1、拆分方式
1)水平拆分
水平拆分即把一個表數據按照行的級別進行拆分,比如表中按每一千萬行爲單位,拆成多個庫表,而它們加起來就是全部數據,特點在於每個分表的表結構都是一樣的。這樣拆分使得每個表數據限制在一定的範圍內,以此來支撐高併發和保證sql的執行效率,一般sql越複雜,要求數據行數約少
2)垂直拆分
垂直拆分即是把一個表中不同列拆分出來,每個庫表包含部分字段,特點就是每個分表的表結構是不同的,一般拆分是將訪問較多的字段拆分出來,數據庫是有緩存的,你訪問頻率高的行字段越少,就可以在緩存裏緩存更多的行,性能就越好
2、拆分策略
1)range方式
就是每庫存一段連續的數據,比如按時間範圍或者連續主鍵分類,如上個月的存在一個庫表,下一月的存在另一個庫表,但這種容易產生熱點問題,因爲多數系統查詢的都是較新的數據,那部分會經常使用而較久遠的數據很少被訪問到
2)hash方式
這種是基於hash算法,根據hash值分配到不同庫表,均勻分散,但可能在擴容的時候沒有上一種方式來的方便
四、拆分思考
1、只選一個sharding column進行分庫分表
2、多個sharding column多個分庫分表
- 冗餘全量
- 冗餘關係
3、sharding column分庫分表 + es
五、使用Sharding-JDBC分庫分表實戰
1、集成過程,大部分配置類似上一篇文章讀寫分離 https://blog.csdn.net/qq_20475615/article/details/99657628
- 首先引入依賴,這裏使用springboot+mybatis-plus+druid+mysql+sharding-jdbc
<!-- starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- MybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.1</version>
</dependency>
<!-- sharding-jdbc -->
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>3.1.0.M1</version>
</dependency>
- application.yml文件配置,配置規則在後面測試過程有解釋
mybatis-plus:
# 放在resource目錄 classpath:/mapper/*Mapper.xml
mapper-locations: classpath:/mapper/*.xml
# 實體掃描,多個package用逗號或者分號分隔
typeAliasesPackage: com.example.project.*.*.mapper
global-config:
# 主鍵類型 0:"數據庫ID自增", 1:"用戶輸入ID",2:"全局唯一ID (數字類型唯一ID)", 3:"全局唯一ID UUID";
id-type: 2
# 字段策略 0:"忽略判斷",1:"非 NULL 判斷",2:"非空判斷"
field-strategy: 2
# 駝峯下劃線轉換
db-column-underline: true
# 刷新mapper 調試神器
refresh-mapper: true
# 數據庫大寫下劃線轉換
#capital-mode: true
# 邏輯刪除配置(下面3個配置)
logic-delete-value: 0
logic-not-delete-value: 1
# SQL 解析緩存,開啓後多租戶 @SqlParser 註解生效
sql-parser-cache: true
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
sharding:
jdbc:
datasource:
names: ds0,ds1
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.1.60:23306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.1.60:63306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
config:
sharding:
props:
sql.show: true
tables:
org_user: #表名
key-generator-column-name: id #主鍵
# actual-data-nodes: ds${0..1}.org_user${0..1} #數據節點
actual-data-nodes: ds${0..1}.org_user${0..1} #數據節點
#分庫策略
database-strategy:
inline:
sharding-column: age
algorithm-expression: ds${age % 2}
#分表策略
table-strategy:
inline:
shardingColumn: sex
algorithm-expression: org_user${sex % 2}
#配置另一個表加多個節點即可
# org_address:
# key-generator-column-name: id
# actual-data-nodes: ds${0..1}.org_address
# database-strategy:
# inline:
# shardingColumn: lit
# algorithm-expression: ds${lit % 2}
- 業務代碼,這裏要說的就是分庫分表後最重要的就是表 id的生成,如何保證它的唯一性如雪花算法之類的,當然這部分不在這裏詳述,這裏直接寫死,重點在於分庫分表部分
//mybatis-plus 配置類
@Configuration
@MapperScan("com.example.project.*.*.mapper") //這裏千萬注意只寫mapper 所在文件夾,mybatis會進行代理,免得誤傷其他文件夾
public class MybatisPlusConfig {
/**
* 分頁插件,自動識別數據庫類型
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
//簡單實體類
@TableName("org_user")
public class User {
private Integer id;
private String name;
private Integer age;
private Integer sex;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
}
//mapper
public interface UserMapper extends BaseMapper<User> {
}
//簡單的服務代碼
@Service
public class UserService {
@Autowired
UserMapper userMapper;
/**
* 測試讀
*/
public void getUser(){
System.out.println(null != userMapper.selectById(1) ? userMapper.selectById(1).getName():"1空");
System.out.println(null != userMapper.selectById(2) ? userMapper.selectById(2).getName():"2空");
System.out.println(null != userMapper.selectById(3) ? userMapper.selectById(3).getName():"3空");
System.out.println(null != userMapper.selectById(4) ? userMapper.selectById(4).getName():"4空");
}
/**
* 測試寫
*/
public void saveUser(int id,int age,int sex,String name){
User user = new User();
user.setId(id);
user.setAge(age);
user.setSex(sex);
user.setName(name);
userMapper.insert(user);
}
}
//測試類
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestService {
@Autowired
UserService userService;
@Test
public void test(){
userService.saveUser(1,2,0,"小米1");//按道理應該去的庫0,org_user0表
userService.saveUser(2,1,0,"小米2");//按道理應該去的庫1,org_user0表
userService.saveUser(3,2,1,"小米3");//按道理應該去的庫0,org_user1表
userService.saveUser(4,1,1,"小米4");//按道理應該去的庫1,org_user1表
// userService.getUser();
}
}
- 在兩個數據庫創建好對應的表,字段對應着實體來就可以,四個表保持結構一致,庫0也就是23306端口,庫1也就是63306端口
- 測試過程,我們配置的庫0 是23306端口,庫1 是63306端口,按照我們配置的規則是,年齡雙數是分到庫0,年齡單數的是分到庫1 ,性別我們用 0 代表男,1 代表女,所以規則是男的分到 org_user0 表,女的分到 org_user1 表,看一下結果是否如我們所想
①測試寫,我們查看結果
②測試讀,查看結果是否能自動路由,正常取出對應的值
2、拓展,類似上一篇文章讀寫分離 https://blog.csdn.net/qq_20475615/article/details/99657628,我們用另一種依賴來配置
- 上面的集成方法中我們用的sharding-jdbc是<artifactId>sharding-jdbc-spring-boot-starter</artifactId>,下面我們換一種依賴來集成,其他的依賴還是跟上面一樣,只要修改sharding-jdbc的
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>4.0.0-RC1</version>
</dependency>
- 接着增加一個sharding-druid.yml,放到和application.yml平級,內容如下,可以看到配置方式跟上面是不一樣的(記住不要把該配置放到application.yml,否則會引起衝突導致啓動失敗),application.yml裏去掉sharding節點那部分,至於mybatis的配置還是跟上面一樣
dataSources:
ds0: !!com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.1.60:23306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
ds1: !!com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.1.60:63306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
shardingRule:
tables:
#表名
org_user:
actualDataNodes: ds${0..1}.org_user${0..1}
#分庫策略
databaseStrategy:
inline:
shardingColumn: age
algorithmExpression: ds${age % 2}
#分表策略
tableStrategy:
inline:
shardingColumn: sex
#這裏不同版本會有不同,開始按官網的配置是algorithmInlineExpression,發現在我的這個版本里報錯,具體看YamlInlineShardingStrategyConfiguration裏面是什麼屬性
algorithmExpression: org_user${sex % 2}
- 增加一個配置類(上面的方式是不需要的)
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() throws Exception {
return YamlShardingDataSourceFactory.createDataSource(ResourceUtils.getFile("classpath:sharding-druid.yml"));
}
}
- 代碼部分跟上面一樣測試即可