導入:
- 一般に、
单表
データ量が 1002000w
% を超えると、メモリにインデックスを保存できないためクエリ速度が非常に遅くなり、後続の SQL クエリでディスク IO が発生し、パフォーマンスが低下します。- 解決策:
mysql 分区
、分表处理
サブライブラリとサブテーブル:
- 理由: データ レベルが一定の規模に達した場合、同時実行性が高すぎて、データベース接続数が十分でない場合
- 目的: テーブルを N 個のテーブルに分割し、各テーブルのデータ範囲を制御し、SQL パフォーマンスを確保します。データ量が以下を超えないようにすることをお勧めします。
500W
コンセプト:
- ライブラリの垂直分割: ビジネスごとにライブラリを分割し、ビジネスごとに異なるデータベースを分割します。
- ハンマーで時計を分割する: 大きな時計を小さな時計に分割します。
- 水平分割: ディメンション標準に従ってデータをさまざまなテーブルに分散し、それらを個別に保存します。またはパーティション、テーブルがディメンションのストレージに従って異なるディスク ファイルに分割され、テーブルは 1 つのままになります。
- 読み取りと書き込みの分離: データ クエリとデータ ストレージは別々に分離されます。
- これは
mysql主从复制
次と同様にsharding-spere插件整合
実装する必要があります
MySQL パーティションの実装:
- バージョンは より大きく
mysql 5.1.3
、パーティション化後も表面上は 1 つのテーブルのままですが、データは複数の場所にハッシュされています。- パーティションの種類:
RANGE
日付、数値サイズなどの列値の指定された連続範囲に基づくパーティションLIST
パーティション、タイプはリスト配列、値がリスト範囲に含まれる場合、そのパーティションに属しますHASH
パーティション、パーティションの数を指定し、テーブルの 1 つ以上の列の値を計算しHash(Key)
、それらを異なるパーティションに分配します。key对应列值为整数
KEY
パーティション化は、ハッシュ パーティション化と同様に、BLOB と Text 以外の他の種類の列をパーティション キーとして指定できます。- 予防:
- パーティション分割を行う場合は、主キーを定義しないか、主キーにパーティション フィールドを追加します。
- パーティションフィールドをNULLにすることはできません
パーティション分割に一般的に使用される SQL:
-- 查询表分区
SELECT table_schema,table_name,partition_name,partition_ordinal_position,partition_method,partition_expression
from information_schema.PARTITIONS
where table_schema = schema()
and table_name = '表名称'
-- 清除表分区,不清除数据
alter table dymc_from_input_info remove partitioning;
-- 定义 表 list 分区 ,表:dymc_from_input_info,字段:template_id
-- 这个业务是,当前表存储数据量很大,数据以模板id导入,可以按模板id分区,所有一个模板id一个分区
ALTER TABLE `dymc_from_input_info` PARTITION BY
LIST(template_id)(
PARTITION p_1613559290235248642 VALUES IN (1613559290235248642),
PARTITION p_1613910831752355842 VALUES IN (1613910831752355842),
PARTITION p_1613910831752355843 VALUES IN (1613910831752355843)
);
-- 添加表list分区,在以有分区再添加list分区
alter table dymc_from_input_info add partition
(
PARTITION p_1 VALUES IN (1)
);
-- 添加 key分区,PARTITIONS 5, 是指定分区数为5
ALTER TABLE `dymc_from_input_info` PARTITION BY KEY(template_id) PARTITIONS 5;
-- 添加 hash分区
ALTER TABLE `dymc_from_input_info` PARTITION BY HASH(template_id) PARTITIONS 3;
-- 查询是否命中分区
EXPLAIN
select * from dymc_from_input_info where template_id=1613559290235248642
パーティションファイルストレージ:
- ストレージ エンジンが InnoDB の場合、
.frm
– テーブル構造用のファイル、.ibd
テーブル データおよびインデックス用のファイル
パーティショニングの前に:
パーティション分割後: .ibd ファイルは 2 つのパーティションに分割され、2 つの
#p#分区名.ibd
ファイルが生成され、複数のパーティションと複数のファイルが個別に保存されます。
コードの統合
@ApiModel(value = "mysql表分区Vo")
@Data
public class MysqlPartitionVo implements Serializable {
private static final long serialVersionUID = -4548301443478563468L;
@ApiModelProperty(value = "库名称")
private String tableSchema;
@ApiModelProperty(value = "表名称")
private String tableName;
@ApiModelProperty(value = "分区名称")
private String partitionName;
@ApiModelProperty(value = "分区位置")
private String partitionOrdinalPosition;
@ApiModelProperty(value = "分区类型")
private String partitionMethod;
@ApiModelProperty(value = "分区字段")
private String partitionExpression;
}
public interface PartitionMapper {
/**
* 查询mysql 对应表-分区列表
* @param tableName 表名称
* @return List<MysqlPartitionVo>
*/
List<MysqlPartitionVo> getPartitionByTable(@Param("tableName") String tableName);
/**
* 删除对应表分区,保留分区
* @param tableName 表名称
* @return Boolean
*/
void removePartitionByTable(@Param("tableName") String tableName);
}
<?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.mapper.PartitionMapper">
<update id="removePartitionByTable">
alter table ${
tableName} remove partitioning;
</update>
<select id="getPartitionByTable" resultType="com.vo.mysql.MysqlPartitionVo">
SELECT table_schema,table_name,partition_name,partition_ordinal_position,partition_method,partition_expression
from information_schema.PARTITIONS
where table_schema = schema()
and table_name = #{
tableName}
</select>
</mapper>
/**
* @author xiaoshu
* @description mysql 分区处理
* @date 2023年01月15日 18:53
*/
public interface PartitionService {
/**
* 初始化分区 表:dymc_from_input_info
*/
void checkPartition();
/**
* 查询该模板是否存在分区,没有则创建
* @param templateId 模板id
* @return Boolean
*/
Boolean checkExistTemplateId(Long templateId);
}
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.constant.CommonConstants;
import com.mapper.DymcFromInputInfoMapper;
import com.mapper.PartitionMapper;
import com.service.PartitionService;
import com.vo.mysql.MysqlPartitionVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author xiaoshu
* @description
* @date 2023年01月15日 18:54
*/
@Slf4j
@Service
public class PartitionServiceImpl implements PartitionService {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Resource
private Redisson redisson;
@Resource
private PartitionMapper partitionMapper;
@Resource
private DymcFromInputInfoMapper dymcFromInputInfoMapper;
private static final String tableName="dymc_from_input_info";
//初始化定义分区sql
private static final String sql =" ALTER TABLE `%s` PARTITION BY\n" +
"LIST(%s)(\n" +
" %s "+
"); ";
//添加分区sql
private static final String add_sql =" ALTER TABLE `%s` add partition( %s )";
private static final String partitionSql="PARTITION %s VALUES IN (%s)";
@Override
public void checkPartition() {
RLock redissonLock = redisson.getLock(CommonConstants.REDIS_LOCK+"partitionTash");
try {
redissonLock.lock();
List<Long> templateIds = dymcFromInputInfoMapper.getTemplateIds();
if (CollectionUtil.isEmpty(templateIds)){
log.info("表单列详情没有数据,不建立分区");
}else {
int requiredSize = templateIds.size();
log.info("需要分区数量:"+ requiredSize);
//分区列表
List<MysqlPartitionVo> partitionByTable = partitionMapper.getPartitionByTable(tableName);
if (CollectionUtil.isNotEmpty(partitionByTable)){
//查询表对应分区数量
List<MysqlPartitionVo> partitionVos = partitionByTable.stream().filter(e -> StrUtil.isNotEmpty(e.getPartitionName())).collect(Collectors.toList());
int actualSize = partitionVos.size();
log.info("实际分区数量:"+ actualSize);
//分区为空
if (CollectionUtil.isEmpty(partitionVos)){
//需要分区数量 > 实际分区数量
log.info("初始化分区");
//拼接分区sql
StringBuilder partitionSql = getPartitionSql(templateIds);
initPartition(partitionSql);
}else {
//分区不为空
if (requiredSize>actualSize){
//添加分区
Map<String, String> templateMap = partitionByTable.stream().collect(Collectors.toMap(MysqlPartitionVo::getPartitionName, MysqlPartitionVo::getPartitionOrdinalPosition));
templateIds.forEach(e->{
String partitionName="p_" + e;
String existFlag = templateMap.get(partitionName);
//不存在分区,模板id
List<Long> unPartitionTemplate = new LinkedList<>();
if (StrUtil.isEmpty(existFlag)){
unPartitionTemplate.add(e);
}
if (CollectionUtil.isNotEmpty(unPartitionTemplate)){
log.info("添加分区数量:"+unPartitionTemplate.size());
//拼接分区sql
StringBuilder partitionSql = getPartitionSql(unPartitionTemplate);
addPartition(partitionSql);
}
});
}
}
}
//清空表分区
//partitionMapper.removePartitionByTable(tableName);
}
}catch (Exception e){
e.printStackTrace();
}finally {
redissonLock.unlock();
}
}
@Override
public Boolean checkExistTemplateId(Long templateId) {
//分区列表
try {
List<MysqlPartitionVo> partitionByTable = partitionMapper.getPartitionByTable(tableName);
if (CollectionUtil.isNotEmpty(partitionByTable)){
//查询表对应分区数量
List<MysqlPartitionVo> partitionVos = partitionByTable.stream().filter(e -> StrUtil.isNotEmpty(e.getPartitionName())).collect(Collectors.toList());
//分区不为空
if (CollectionUtil.isNotEmpty(partitionVos)){
log.info("当前分区数量:"+partitionVos.size());
//已有分区map
Map<String, String> templatePartitionMap = partitionByTable.stream().collect(Collectors.toMap(MysqlPartitionVo::getPartitionName, MysqlPartitionVo::getPartitionOrdinalPosition));
String partitionName = templatePartitionMap.get(String.valueOf(templateId));
//如果不存在分区
if (StrUtil.isEmpty(partitionName)){
partitionName="p_"+templateId;
log.info("该分区不存在:"+partitionName);
StringBuilder partitionSql = getPartitionSql(Collections.singletonList(templateId));
//添加分区
addPartition(partitionSql);
}
}else {
//分区为空
String partitionName = "p_"+templateId;
log.info("该分区不存在:"+partitionName);
StringBuilder partitionSql = getPartitionSql(Collections.singletonList(templateId));
//初始化分区
initPartition(partitionSql);
}
return Boolean.TRUE;
}
}catch (Exception e){
return Boolean.FALSE;
}
return Boolean.FALSE;
}
/**
* 拼接分区sql
* @param templateIds 待添加分区模板id列表
* @return partitionSql
*/
private synchronized StringBuilder getPartitionSql(List<Long> templateIds) {
List<String> partitionSqls = new LinkedList<>();
//拼接分区sql
for (Long templateId : templateIds) {
String partitionName = String.format(partitionSql, "p_" + templateId, templateId);
partitionSqls.add(partitionName);
}
StringBuilder partitionSql= new StringBuilder();
for (int i = 0; i < partitionSqls.size(); i++) {
if (i!=partitionSqls.size()-1){
partitionSql.append(partitionSqls.get(i)).append(",");
}else {
partitionSql.append(partitionSqls.get(i));
}
}
return partitionSql;
}
/**
* 定义表分区
* @param partitionSql 分区sql
*/
private synchronized void initPartition(StringBuilder partitionSql) {
Connection connection;
Statement statement;
String executeSql = String.format(sql, tableName, "template_id", partitionSql);
try {
connection = DriverManager.getConnection(url, username, password);
statement = connection.createStatement();
statement.execute(executeSql);
log.info("分区添加成功");
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 添加表分区
* @param partitionSql 分区sql
*/
private synchronized void addPartition(StringBuilder partitionSql) {
Connection connection;
Statement statement;
String executeSql = String.format(add_sql, tableName,partitionSql);
try {
connection = DriverManager.getConnection(url, username, password);
statement = connection.createStatement();
statement.execute(executeSql);
log.info("分区添加成功");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
シャーディング球の概念
Apache ShardingSphere
設計理念は Database Plus で、異種データベースの上位層の標準とエコロジーを構築することを目的としています。ShardingSphere-JDBC
軽量の Java フレームワークとして位置付けられ、Java の JDBC 層で追加サービスを提供します。ShardingSphere-Proxy
透過的なデータベース エージェントとして位置付けられ、データベース バイナリ プロトコルを実装することで異種言語のサポートを提供します。- 公式ウェブサイト:https ://shardingsphere.incubator.apache.org/index_zh.html
フローチャート:
sharding-shpere は読み取りと書き込みの分離を実装します。
- 読み取りと書き込みの分離を実現するには、最初に設定する必要があります
mysql的 主从复制
。次に、sharding-shpere を使用して読み取りと書き込みの分離を実現します。
mysqlのマスター/スレーブレプリケーション設定:
- マスター/スレーブ レプリケーション (AB レプリケーションとも呼ばれる) を使用すると、1 つの MySQL データベース サーバー (マスター サーバー) からのデータを 1 つ以上の MySQL データベース サーバー (スレーブ サーバー) にレプリケートできます。
流程概念:
データベースのバイナリログ機能をオンにすると、データ操作記録(データ変更)がバイナリログバイナリファイルに保存され、TCP接続を通じてスレーブライブラリのリレーログファイル(リレーログ)に報告され、SQLが実行されます。データの同期を実現するためにステートメントが再実行されます。
Mysql のマスター/スレーブ構成では、例として 5.7.10 を使用します。
- メインデータベース構成ファイル /etc/my.cnf を変更し、バイナリログ機能を有効にして、サーバーを再起動します。
[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M
## 同一局域网内注意要唯一
server-id=100
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
## 复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed
validate_password_policy=0
validate_password_length=0
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
- マスター サービスとスレーブ サービスが ping を送信してファイアウォールを開くことができることを確認してください。
- リモートアクセスを確保する
mysql -uroot -p
、データベースにログインします;show master status;
- バイナリ ファイルのステータスを表示します
-- 修改远程访问 ,'%' 可为从服务器ip
use mysql;
update user set host = '%' where user = 'root';
FLUSH PRIVILEGES;
- mysql からデータ構成を変更することに同意し、
server-id 不一致
- サーバーからマスターサーバーへSQLバインディングを実行します。
master_log_file
、master_log_pos
次にshow master status;
表示するコマンド
change master to master_host='master服务器ip', master_user='root',
master_password='master密码', master_port=3306, master_log_file='mysql-bin.000002',master_log_pos=2079;
- 同期ステータスのクエリ
show slave status\G;
問題の処理:
--建议处理:
stop slave;
--删除多余库
set global sql_slave_skip_counter=1;
start slave;
show slave status\G ;
コードの統合:
<!-- <sharding-sphere.version>4.0.0-RC1</sharding-sphere.version> -->
<!--依赖sharding-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-core-common</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
読み取りと書き込みの分離構成:
spring:
#读写分离配置
shardingsphere:
# 参数配置,显示sql
props:
sql:
show: true
# 配置数据源
datasource:
# 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
names: master,slave
# 给master-ds1每个数据源配置数据库连接信息
master:
# 配置druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.21:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: 123456
maxPoolSize: 100
minPoolSize: 5
# 配置slave
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: 730730
maxPoolSize: 100
minPoolSize: 5
# 配置默认数据源ds1
sharding:
# 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
default-data-source-name: master
# 配置数据源的读写分离,但是数据库一定要做主从复制
masterslave:
# 配置主从名称,可以任意取名字
name: ms
# 配置主库master,负责数据的写入
master-data-source-name: master
# 配置从库slave节点
slave-data-source-names: slave
# 配置slave节点的负载均衡均衡策略,采用轮询机制
load-balance-algorithm-type: round_robin
統合が成功しました:
スレーブデータベースにクエリを実行する
メインライブラリに書き込む
データシャーディングを構成します。
- サブテーブルの概念:
users
テーブルが処理される場合、users
そのテーブルは逻辑表
実際には存在しません。
- yml で設定されたルールに従って、最終的に users_1 または users_2 テーブルがヒットします。
yml設定
- 式の説明:
ds$->{0..1}
、DS コード内で上記で指定されたデータベース エイリアス、シャーディングがヒットした場合は、対応する範囲表达式的值
の値$->{0..N}
spring:
#数据分片配置
shardingsphere:
# 参数配置,显示sql
props:
sql:
show: true
# 配置数据源
datasource:
# 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
names: ds0,ds1
# 给master-ds1每个数据源配置数据库连接信息
ds0:
# 配置druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.21:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: 123456
maxPoolSize: 100
minPoolSize: 5
# 配置ds2-slave
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: 730730
maxPoolSize: 100
minPoolSize: 5
# 配置默认数据源ds1
sharding:
# 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
default-data-source-name: ds0
# 配置分表的规则
tables:
# users 逻辑表名
users:
# 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
actual-data-nodes: ds$->{
0..1}.users$->{
0..1}
# 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
database-strategy:
inline:
sharding-column: age # 分片字段(分片键)
algorithm-expression: ds$->{
age % 2} # 分片算法表达式
# 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
table-strategy:
inline:
sharding-column: age # 分片字段(分片键)
algorithm-expression: users$->{
age % 2} # 分片算法表达式
データ age=20 を送信すると ds0 がヒットし、
データ age=21 を送信すると ds1 がヒットします。
日付によるなどの標準シャーディングは必須です
implements PreciseShardingAlgorithm
。
spring:
#读写分离配置
shardingsphere:
# 参数配置,显示sql
props:
sql:
show: true
# 配置数据源
datasource:
# 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
names: ds0,ds1
# 给master-ds1每个数据源配置数据库连接信息
ds0:
# 配置druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.21:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: 123456
maxPoolSize: 100
minPoolSize: 5
# 配置ds2-slave
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/sharding-test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: 730730
maxPoolSize: 100
minPoolSize: 5
# 配置默认数据源ds1
sharding:
# 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
default-data-source-name: ds0
# 配置分表的规则
tables:
# users 逻辑表名
users:
# 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
actual-data-nodes: ds$->{
0..1}.users$->{
0..1}
# 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
database-strategy:
standard:
sharding-column: birthday # 分片字段(分片键)
preciseAlgorithmClassName: com.config.BirthdayAlgorithm
# 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
table-strategy:
inline:
sharding-column: age # 分片字段(分片键)
algorithm-expression: users$->{
age % 2} # 分片算法表达式
具体的なルールを書きます。