文章目录
背景
一般来说数据库单表超过2000W性能就会急剧下降,我们就需要做相应的处理,分库分表,或者使用分布式数据库,而企业中较为常见的是分库分表。
分库分表目前主流的框架有两种,一种是MyCat,另一种是 ShardingSphere,就目前两种框架的整体功能来看应该是差不多,都是仅支持基本SQL92语法,复杂特殊的语法一般不支持。而Mycat是以中间件的方式给你做SQL聚合,需要搭建一个MyCat中间件,同时也要保证Mycat的高可用,如果MyCat一单挂掉,整个系统就挂了,使用MyCat变相增加了系统的复杂性和可维护性。相反 ShardingSphere就不一样了,以jar的形式分库分表,我们只需要引入相关依赖作相应配置即可完成分库分表,也是目前主流使用的分库分表解决方式。本次我们就来研究一些Sharding-Jdbc
分库分表
这里我们目前的打算分四个库,每个库2个表,以订单表为例
数据库结构如下:
db_00--
|--t_order_0000
|--t_order_0001
db_01--
|--t_order_0000
|--t_order_0001
db_02--
|--t_order_0000
|--t_order_0001
db_03--
|--t_order_0000
|--t_order_0001
建表准备
分别在 db_00,db_01,db_02.db_03创建 t_order_0000, t_order_0001
DROP TABLE IF EXISTS `t_order_0000`;
CREATE TABLE `t_order_0000` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) DEFAULT NULL,
`order_id` bigint(20) DEFAULT NULL,
`user_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1239 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_order_0001
-- ----------------------------
DROP TABLE IF EXISTS `t_order_0001`;
CREATE TABLE `t_order_0001` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) DEFAULT NULL,
`order_id` bigint(20) DEFAULT NULL,
`user_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1239 DEFAULT CHARSET=utf8;
同时在ds0单独见一个order_config表
DROP TABLE IF EXISTS `order_config`;
CREATE TABLE `order_config` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`pay_timeout` int(11) DEFAULT NULL COMMENT '支付超时时间;单位:分钟',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单配置表';
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 实现对 MyBatis 的自动化配置 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- 实现对 Sharding-JDBC 的自动化配置 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC2</version>
</dependency>
<!-- 方便等会写单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
说明: sharding-jdbc-spring-boot-starter 暂时不要使用最新的 4.0.0-RC3,会有一些奇怪bug,
连接池尽量不要加入 druid,不然也会出现sharing-jdbc 的DataSource冲突,如果非要用,可自行百度找解决方法,
这是官网给出的说明
配置文件
Sharding-Jdbc 使用起来非常简单,难点就是核心配置文件,本次我们使用默认的分片策略
# ds1
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/ds0?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# ds1
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/ds1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456
# ds2
spring.shardingsphere.datasource.ds2.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds2.jdbc-url=jdbc:mysql://localhost:3306/ds2?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.ds2.username=root
spring.shardingsphere.datasource.ds2.password=123456
# ds3
spring.shardingsphere.datasource.ds3.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds3.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds3.jdbc-url=jdbc:mysql://localhost:3306/ds3?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.ds3.username=root
spring.shardingsphere.datasource.ds3.password=123456
#数据源名称,多数据源以逗号分隔
spring.shardingsphere.datasource.names=ds0,ds1,ds2,ds3
#未配置分片规则的表将通过默认数据源定位-适用于单库单表,该表无需配置分片规则
spring.shardingsphere.sharding.default-data-source-name=ds0
# 分库 以 user_id 为分片键,因为分四个库,所以取模 4,虚拟表名为 t_order
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression=ds$->{user_id % 4}
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column=user_id
# 分表 分片键位order_id,每个库分为两表,所以取模2
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_000$->{order_id % 2}
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id
# 这里由于分库分表字段不相同配置,不然会导致使用user_id 查询找不到相应的表,如果我们分库分表都使用 user_id 则不需要这个配置
spring.shardingsphere.sharding..tables.t_order.actual-data-nodes = ds$->{0..3}.t_order_000$->{0..1}
#是否开启SQL显示,默认值: false
spring.shardingsphere.props.sql.show= true
# mybatsi 配置
# 别名
mybatis.type-aliases-package=com.zou.entity
mybatis.mapper-locations=classpath:mapper/*.xml
# 开启驼峰自动转大写
mybatis.configuration.mapUnderscoreToCamelCase=true
其他实体类和dao
Order
@Data
@ToString
public class Order {
private String id;
private Long userId;
private Long orderId;
private String userName;
}
OrderConfig
@Data
public class OrderConfig {
private Integer id;
private Integer payTimeout;
}
OrderDao
@Repository
public interface OrderDao {
// 查询某个用户订单列表
List<Order> getOrders(Order order);
// 插入订单信息
int addOrder(Order orderInfo);
}
OrderConfigDao
@Repository
public interface OrderConfigDao {
List<OrderConfig> getOrderConfig();
}
OrderMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zou.dao.OrderDao">
<select id="getOrders" parameterType="Order"
resultType="Order">
select
t.id ,
t.user_id ,
t.order_id ,
t.user_name
from t_order t
where t.user_id=#{userId}
</select>
<insert id="addOrder" parameterType="Order" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_order (
user_id,user_name,order_id
) VALUES (
#{userId},#{userName},#{orderId}
)
</insert>
</mapper>
OrderConfigMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zou.dao.OrderConfigDao">
<select id="getOrderConfig" parameterType="Integer" resultType="OrderConfig">
SELECT *
FROM order_config
</select>
</mapper>
测试
@SpringBootTest(classes = ShardingSphereApplication.class)
@RunWith(SpringRunner.class)
public class OrderDaoTest {
@Autowired
OrderDao orderDao;
@Test
public void insert() {
for (int i = 0; i < 1000; i++) {
long userId = i;
long orderId = i + 1;
Order order = new Order();
order.setUserName("阿离");
order.setUserId(userId);
order.setOrderId(orderId);
int result = orderDao.addOrder(order);
}
}
}
插入数据逻辑:
userId = 0 --> 0%4 = 0 对应数据库 db0
orderId = 1 --> 1%2 = 1 对应表 t_order_0001
所以第一条数据就落到 db0数据库的 t_order_0001表
其他的也是如此
查询
@Test
public void testQueryList() {
Order order = new Order();
order.setUserId(2l);
List<Order> list = orderDao.getOrders(order);
list.forEach(s -> System.out.println(s));
}
由于我们只指定了分库片 userId查询,所以这里就会查询 ds2数据库,但是无法确定是那张表,所以会查询所有表,然后将所有表的结果归并返回,我们看sql可以看出
@Test
public void getOrders() {
Order order = new Order();
order.setUserId(8l);
order.setOrderId(8l);
orderDao.getOrders(order);
}
如果我们指定 userId 和 orderId就可以找到对应的库和表,就只会查询一张表
如果我们查询默认没有分库分表的普通表 order_config,则会自动去我们配置的默认数据库去找到相应的表
@Autowired
OrderConfigDao orderConfigDao;
@Test
public void getOrderConfig() {
orderConfigDao.getOrderConfig();
}