以商城商品为例
垂直分表:将一个表按照字段分成多表,每个表存储其中一部分字段
它带来的提升是:
1、为了避免IO争抢并减少锁表的几率,查看详情的用户与商品信息浏览相互不影响
2、充分发挥热门数据的操作效率,商品信息的操作的高效率不会被商品描述的低效率所拖累
<!--为什么大字段IO效率低:第一是由于数据量本身大,需要更长的读取时间;第二是跨页,页是数据库存储单位,很多查找及定位操作都是以页为单位,单页内的数据行越多数据库整体性能越好,而大字段占用空间大,单页内存储行数少,因此IO效率较低。第三,数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能 -->
垂直分库:
垂直分库是指按照业务将表进行分类,分页到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用
它带来的提升是:
1、解决业务层面的耦合,业务清晰
2、能对不同业务的数据进行分组管理,维护,监控,扩展等
3、高并发场景下,垂直分库一定程度的提升IO,数据库连接数,降低单机硬件资源的瓶颈
垂直分库通过将表按业务分类,然后分布在不同数据库,并且可以将这些数据库部署在不同服务器上,从而达到多个服务器共同分摊压力的效果,但是依然没有解决单表数据量过大的问题。
水平分库:
是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。
它带来的提升是:
1、解决了单库大数据,高并发的性能瓶颈
2、提高了系统的稳定性及可用性。稳定性体现在IO冲突少,锁定减少,可用性指某个库出问题,部分数据可用
水平分表:
水平分表是在同一个数据库内,把同一个表的数据按一定规则拆到多个表中
它带来的提升是:
1、优化单一表数据量过大而产生的性能问题
2、避免IO争抢并减少锁表的几率
分库分表带来的问题:
1、事务一致性问题
由于分库分表把数据分页在不同库甚至不同服务器,不可避免分带来分页式事务问题
2、跨节点关联查询
3、跨节点分页、排序、函数
4、主键重复问题
在分库分表环境中,由于表中数据同时存在不同的数据库中,主键值平时使用的自增长 将无用武之地,某个分区数据库生成的ID无法保证全局唯一。因此需要单独设计全局主键,以避免跨库主键重复问题
5、公共表
实际的应用场景中,参数表,数据字典表等都是数据量较少,变动少,而且属于高频联合查询的依赖表。
可以将这类表在每个数据库都保存一份,所有对公共表的更新操作都同时发送到所有分库执行
由于分库分表之后,数据被分散在不同的数据库、服务器。因此,对数据的操作也就无法通过常规方式完成,并且它还带来了一系列的问题。
Sharding-jdbc介绍
sharding-jdbc是当当网研发的开源分页式数据库中间件,从3.0开始Sharding-jdbc被包含在Sharding-Sphere,之后该项目进入 Apache,4.0版本之后的版本为Apache版本
Sharding-jdbc它定位为级Java框架,在java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
Sharding-jdbc的核心功能为数据分片和读写分离,通过Sharding-JDBC,应用可以透明的使用jdbc访问已经分库分表、读写分离的多个数据源,而不用关心数据源的数量以及数据如何分页。
与jdbc性能对比:
1、性能损耗测试:服务器资源充足、并发数相同,比较JDBC和Sharding-JDBC性能损耗,sharding-jdbc相对JDBC损耗不超过7%
2、性能对比测试:服务器资源使用到极限,相同场景JDBC与sharding-jdbc的吞吐量相当
3、性能对比测试:服务器资源使用到极限,sharding-jdbc采用分库分表后,sharding-jdbc吞吐量较JDBC不分表有接近2倍提升
sharding-jdbc快速入门
使用sharding-jdbc完成对订单表的水平分表,通过快速入门程序的开发,快速体验sharding-jdbc的使用方法。
人工创建两张表,t_order_1和t_order_2,这两张表是订单表拆分后的表,通过sharding-jdbc向订单表插入数据,按照一定的分片规则,主键为偶数的进入t_order_1,另一部分数据进入t_order_2,通过sharding-jdbc查询数据,根据SQL语句的内容从t_order_1或t_order_2查询数据。
代码mode(水平分表):
配置yml
server:
port: 8082
servlet:
context-path: /sharding-jdbc-simple-demo
spring:
application:
name: sharding-jdbc-simple-demo
http:
encoding:
enabled: true
charset: UTF-8
force: true
#shrading-jdbc配置
shardingsphere:
datasource:
names: m1
m1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shareding?useUnicode=true
username: root
password: 123456
sharding: #指定t_order表的数据分页情况,配置数据节点 m1.t_order_1 m1.t_order_2
tables:
t_order:
actual-data-nodes: m1.t_order_$->{1..2}
key-generator:
column: id #主键字段
type: SNOWFLAKE #指定t_order表主键生成策略
table-strategy: #指定t_order表的分片策略,分片策略包括分片键和分片算法
inline:
sharding-column: id #分片键
algorithm-expression: t_order_$->{id%2+1} #分片算法
props:
sql:
show: true #输出sharding-jdbc的真实sql
mybatis:
configuration:
map-underscore-to-camel-case: true
swagger:
enable: true
logging:
level:
root: info
org:
springframeword:
web: info
cn:
ping: debug
druid:
sql: debug
maven:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sharding-jdbc-simple</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</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>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
mysql创建表:
CREATE TABLE `t_order_1` (
`id` bigint(20) NOT NULL COMMENT '订单id',
`price` decimal(10,2) NOT NULL COMMENT '订单价格',
`user_id` bigint(20) NOT NULL COMMENT '下单用户id',
`status` varchar(50) NOT NULL COMMENT '订单状态',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `t_order_2` (
`id` bigint(20) NOT NULL COMMENT '订单id',
`price` decimal(10,2) NOT NULL COMMENT '订单价格',
`user_id` bigint(20) NOT NULL COMMENT '下单用户id',
`status` varchar(50) NOT NULL COMMENT '订单状态',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
实体类:
package cn.ping.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author: yejianping
* @date: 2022/12/5 15:12:25
* @email: 1152665905@qq.com
* @Description:
*/
@Data
@Builder
@TableName("t_order")
public class Order {
private Long id;
private BigDecimal price;
private Long userId;
private String status;
}
mapper类:
package cn.ping.mapper;
import cn.ping.entity.Order;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author: yejianping
* @date: 2022/12/5 15:14:17
* @email: 1152665905@qq.com
* @Description:
*/
public interface OrderMapper extends BaseMapper<Order> {
List<Order> selectOrderList(@Param("position") Integer startPosition, @Param("size")Integer size);
}
mapper.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="cn.ping.mapper.OrderMapper">
<select id="selectOrderList" resultType="cn.ping.entity.Order">
select * from t_order order by price limit #{position} ,#{size}
</select>
</mapper>
service
package cn.ping.server;
import cn.ping.entity.Order;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* @author: yejianping
* @date: 2022/12/5 15:16:52
* @email: 1152665905@qq.com
* @Description:
*/
public interface IOrderService extends IService<Order> {
/**
* 通过ids查询Order
* @param ids
* @return
*/
List<Order> getOrderByIds(List<Long> ids);
IPage<Order> getOrderPage(Integer page,Integer size);
List<Order> getOrderList(Integer page,Integer size);
}
serviceImpl
package cn.ping.server.impl;
import cn.ping.entity.Order;
import cn.ping.mapper.OrderMapper;
import cn.ping.server.IOrderService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* @author: yejianping
* @date: 2022/12/5 15:21:06
* @email: 1152665905@qq.com
* @Description:
*/
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Override
public List<Order> getOrderByIds(List<Long> ids) {
if(CollectionUtils.isEmpty(ids)){
return null;
}
return this.listByIds(ids);
}
@Override
public IPage<Order> getOrderPage(Integer page, Integer size) {
LambdaQueryWrapper<Order> lqw = new LambdaQueryWrapper();
lqw.orderByDesc(Order::getPrice);
return page(new Page<>(page,size),lqw);
}
@Override
public List<Order> getOrderList(Integer page,Integer size){
return this.baseMapper.selectOrderList((page-1)*size,size);
}
}
测试类
package cn.ping;
import cn.ping.entity.Dog;
import cn.ping.entity.Order;
import cn.ping.server.IDogService;
import cn.ping.server.IOrderService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
* @author: yejianping
* @date: 2022/12/5 15:22:35
* @email: 1152665905@qq.com
* @Description:
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {ShardingJdbcSimpleApplication.class})
@Slf4j
public class OrderTest {
@Autowired
private IOrderService orderService;
@Autowired
private IDogService dogService;
@Test
public void testOrderInsert(){
List<Order> orderList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
Order order = Order.builder()
.price(new BigDecimal(new Random().nextInt(100)))
.status("SUCCESS")
.userId(1L)
.build();
orderList.add(order);
}
orderService.saveBatch(orderList);
/*Order order = Order.builder()
.price(new BigDecimal(10))
.status("SUCCESS")
.userId(1L)
.build();
orderService.save(order);*/
}
@Test
public void testFindByIds(){
Long[] ids = {1599673041937334275L,1599673040175726594L};
List<Order> orderByIds = orderService.getOrderByIds(Arrays.asList(ids));
for (Order order : orderByIds) {
log.info(order.toString());
}
}
@Test
public void testOrderPage(){
IPage<Order> orderPage = orderService.getOrderPage(1, 10);
for (Order order : orderPage.getRecords()) {
log.info(order.toString());
}
}
@Test
public void testOrderList(){
List<Order> orderList = orderService.getOrderList(1, 10);
for (Order order : orderList) {
log.info(order.toString());
}
}
}
sharding-jdbc和mybatis plus分页冲突,不能使用分页插件,查询总数有问题