1.为什么要分库Or分表
不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞吐量、崩溃)。
(1)IO瓶颈
第一种:磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的IO,降低查询速度 -> 分库和垂直分表。
第二种:网络IO瓶颈,请求的数据太多,网络带宽不够 -> 分库。
(2)CPU瓶颈
第一种:SQL问题,如SQL中包含join,group by,order by,非索引字段条件查询等,增加CPU运算的操作 -> SQL优化,建立合适的索引,在业务Service层进行业务计算。
第二种:单表数据量太大,查询时扫描的行太多,SQL效率低,CPU率先出现瓶颈 -> 水平分表。
今天我们着重看一下水平分库分表
2.步骤
我这里选择用一个新的MySQL来演示,首先用Docker启动一个新的MySQL,端口为3308
docker run -p 3308:3306 --name mysql-shardingsphere -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
分库分表是需要手动完成的,我们这里新建数据库ds0、ds1,每个数据库里有user_0、user_1两张表
ds0建表语句
CREATE DATABASE IF NOT EXISTS `ds0`;
USE `ds0`;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `user_0`;
CREATE TABLE `user_0` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `user_1`;
CREATE TABLE `user_1` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `user_2`;
CREATE TABLE `user_1` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SET FOREIGN_KEY_CHECKS = 1;
ds1建表语句
CREATE DATABASE IF NOT EXISTS `ds1`;
USE `ds1`;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `user_0`;
CREATE TABLE `user_0` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `user_1`;
CREATE TABLE `user_1` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `user_2`;
CREATE TABLE `user_1` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SET FOREIGN_KEY_CHECKS = 1;
然后我们新建一个SpringBoot项目,导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- DB: MyBatis Plus, Druid -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
然后是User实体类与Mapper
@Data
@TableName("user")
@Accessors(chain = true)
public class User {
/**
* 主键Id
*/
private int id;
/**
* 名称
*/
private String name;
/**
* 年龄
*/
private int age;
}
package com.zym.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zym.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class TestSharding {
@Autowired
private UserMapper userMapper;
@Test
public void insertUser(){
for (int i = 1; i <= 20; i++) {
User user = new User();
user.setId(i).setAge(i+10).setName("test"+i);
userMapper.insert(user);
}
}
@Test
public void getAllUser(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("id");
List<User> userList = userMapper.selectList(queryWrapper);
for (User user : userList) {
System.out.println(user.toString());
}
}
}
配置类:
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.60.10:3308/ds0?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.60.10:3308/ds1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
sharding:
tables:
user:
actualDataNodes: ds$->{0..1}.user_$->{0..2}
# 分库策略
databaseStrategy:
inline:
shardingColumn: id
algorithmExpression: ds${id % 2}
# 分表策略
tableStrategy:
inline:
shardingColumn: age
algorithmExpression: user_${age % 3}
props:
sql:
show: true
main:
allow-bean-definition-overriding: true
然后我们来进行测试
首先插入100个用户后查看结果:
然后我们再执行查询所有用户的方法:
2021-08-20 09:35:02.303 INFO 14644 --- [ main] ShardingSphere-SQL : Rule Type: sharding
2021-08-20 09:35:02.304 INFO 14644 --- [ main] ShardingSphere-SQL : Logic SQL: SELECT id,name,age FROM user
ORDER BY id ASC
2021-08-20 09:35:02.305 INFO 14644 --- [ main] ShardingSphere-SQL : SQLStatement: SelectStatement(super=DQLStatement(super=AbstractSQLStatement(type=DQL, tables=Tables(tables=[Table(name=user, alias=Optional.absent())]), routeConditions=Conditions(orCondition=OrCondition(andConditions=[])), encryptConditions=Conditions(orCondition=OrCondition(andConditions=[])), sqlTokens=[TableToken(tableName=user, quoteCharacter=NONE, schemaNameLength=0)], parametersIndex=0, logicSQL=SELECT id,name,age FROM user
ORDER BY id ASC)), containStar=false, firstSelectItemStartIndex=8, selectListStopIndex=18, groupByLastIndex=0, items=[CommonSelectItem(expression=id, alias=Optional.absent()), CommonSelectItem(expression=name, alias=Optional.absent()), CommonSelectItem(expression=age, alias=Optional.absent())], groupByItems=[], orderByItems=[OrderItem(owner=Optional.absent(), name=Optional.of(id), orderDirection=ASC, nullOrderDirection=ASC, index=-1, expression=null, alias=Optional.absent())], limit=null, subqueryStatement=null, subqueryStatements=[], subqueryConditions=[])
2021-08-20 09:35:02.305 INFO 14644 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: SELECT id,name,age FROM user_0
ORDER BY id ASC
2021-08-20 09:35:02.305 INFO 14644 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: SELECT id,name,age FROM user_1
ORDER BY id ASC
2021-08-20 09:35:02.305 INFO 14644 --- [ main] ShardingSphere-SQL : Actual SQL: ds0 ::: SELECT id,name,age FROM user_2
ORDER BY id ASC
2021-08-20 09:35:02.305 INFO 14644 --- [ main] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,name,age FROM user_0
ORDER BY id ASC
2021-08-20 09:35:02.305 INFO 14644 --- [ main] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,name,age FROM user_1
ORDER BY id ASC
2021-08-20 09:35:02.305 INFO 14644 --- [ main] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,name,age FROM user_2
ORDER BY id ASC
User(id=1, name=test1, age=1)
User(id=2, name=test2, age=2)
User(id=3, name=test3, age=3)
User(id=4, name=test4, age=4)
User(id=5, name=test5, age=5)
User(id=6, name=test6, age=6)
User(id=7, name=test7, age=7)
User(id=8, name=test8, age=8)
User(id=9, name=test9, age=9)
User(id=10, name=test10, age=10)
User(id=11, name=test11, age=11)
User(id=12, name=test12, age=12)
User(id=13, name=test13, age=13)
User(id=14, name=test14, age=14)
User(id=15, name=test15, age=15)
User(id=16, name=test16, age=16)
User(id=17, name=test17, age=17)
User(id=18, name=test18, age=18)
User(id=19, name=test19, age=19)
User(id=20, name=test20, age=20)
User(id=21, name=test21, age=21)
User(id=22, name=test22, age=22)
User(id=23, name=test23, age=23)
User(id=24, name=test24, age=24)
User(id=25, name=test25, age=25)
User(id=26, name=test26, age=26)
User(id=27, name=test27, age=27)
User(id=28, name=test28, age=28)
User(id=29, name=test29, age=29)
User(id=30, name=test30, age=30)
User(id=31, name=test31, age=31)
User(id=32, name=test32, age=32)
User(id=33, name=test33, age=33)
User(id=34, name=test34, age=34)
User(id=35, name=test35, age=35)
User(id=36, name=test36, age=36)
User(id=37, name=test37, age=37)
User(id=38, name=test38, age=38)
User(id=39, name=test39, age=39)
User(id=40, name=test40, age=40)
User(id=41, name=test41, age=41)
User(id=42, name=test42, age=42)
User(id=43, name=test43, age=43)
User(id=44, name=test44, age=44)
User(id=45, name=test45, age=45)
User(id=46, name=test46, age=46)
User(id=47, name=test47, age=47)
User(id=48, name=test48, age=48)
User(id=49, name=test49, age=49)
User(id=50, name=test50, age=50)
User(id=51, name=test51, age=51)
User(id=52, name=test52, age=52)
User(id=53, name=test53, age=53)
User(id=54, name=test54, age=54)
User(id=55, name=test55, age=55)
User(id=56, name=test56, age=56)
User(id=57, name=test57, age=57)
User(id=58, name=test58, age=58)
User(id=59, name=test59, age=59)
User(id=60, name=test60, age=60)
User(id=61, name=test61, age=61)
User(id=62, name=test62, age=62)
User(id=63, name=test63, age=63)
User(id=64, name=test64, age=64)
User(id=65, name=test65, age=65)
User(id=66, name=test66, age=66)
User(id=67, name=test67, age=67)
User(id=68, name=test68, age=68)
User(id=69, name=test69, age=69)
User(id=70, name=test70, age=70)
User(id=71, name=test71, age=71)
User(id=72, name=test72, age=72)
User(id=73, name=test73, age=73)
User(id=74, name=test74, age=74)
User(id=75, name=test75, age=75)
User(id=76, name=test76, age=76)
User(id=77, name=test77, age=77)
User(id=78, name=test78, age=78)
User(id=79, name=test79, age=79)
User(id=80, name=test80, age=80)
User(id=81, name=test81, age=81)
User(id=82, name=test82, age=82)
User(id=83, name=test83, age=83)
User(id=84, name=test84, age=84)
User(id=85, name=test85, age=85)
User(id=86, name=test86, age=86)
User(id=87, name=test87, age=87)
User(id=88, name=test88, age=88)
User(id=89, name=test89, age=89)
User(id=90, name=test90, age=90)
User(id=91, name=test91, age=91)
User(id=92, name=test92, age=92)
User(id=93, name=test93, age=93)
User(id=94, name=test94, age=94)
User(id=95, name=test95, age=95)
User(id=96, name=test96, age=96)
User(id=97, name=test97, age=97)
User(id=98, name=test98, age=98)
User(id=99, name=test99, age=99)
User(id=100, name=test100, age=100)
根据控制台可以看到,即使是分库分表了,加入查询条件依然可以查询出来,代码零侵入~
这样一个简单的分库分表就实现了,当然也可以搭配读写分离一起使用