数据库优化系列之分表实践与思考
文章目录
前情提要 : 在学习中接触到分库分表这个概念,也通过简单的方式,啊就是听人说去了解过,但是没时间过,停留在"看过猪跑" 那种阶段,然后嗯在之前的一段时间(几个月内)有了些实践的经验,知道国庆假期最后一段时光,完成了一小次的闭环,特此写下本章笔记.
问题 :
- 假如你要存全国人口信息,为什么不一张表存储,分开存放我查找起来麻烦,我为什么一定要对信息分开存放.
- 分开存放的本质没有意义啊,数据量本质还是没变小,分开我查找起来还费劲,为什么不单独建一个表以关联的形式存放信息,那样更快也容易操作?
- 一定要分表吗?
以下内容主要包含,简单介绍分表,引入前人的经验值,一些其他前辈写的数据分析博客,两次简单的分表实践,会在过程中提出很多问题,会回答一部分,但也有些新的坑挖出来希望有时间可以填补.
什么是分表?为什么要分表?
分表是指将一个大表按照某种规则或条件拆分成多个小表,每个小表称为分表。这样做的目的是为了提高数据库的查询和写入性能,减轻单表的数据量压力。
- 提高查询性能: 大表的查询速度通常会变慢,通过分表可以将数据分散到多个小表,提高查询速度。
- 减轻单表压力: 单个表中的数据量过大会导致索引失效、写入缓慢等问题,分表可以将数据均匀分散,降低单表的数据量。
- 方便数据维护: 分表可以使得维护更加灵活,比如针对某些历史数据的维护可以更加精细化。
- 提高系统可扩展性: 当业务规模不断扩大时,分表可以更容易地进行水平扩展,分布式数据库系统可以更好地处理多个小表。
- 降低锁竞争: 大表的写入操作可能会导致锁竞争,通过分表可以降低锁的粒度,减少锁竞争。
我将其总结为,分摊火力,当然除了这些意外,还有一种情况,就是业务逻辑需要,除了性能以外业务上也可能需要这种方式,类似于如何把大象装进冰箱的问题,要么冰箱足够大,要么冰箱足够多,肢解掉大象自然能进冰箱了(大象:"要命.jpg"),分而化之的思想除了编程领域其他地方也用的到,分表就是这种思想下的一种方式,同类型的还有分布式加机器,数仓(啊我前一个月刚知道这玩意),分段传输数据(学名叫啥我忘了).
好吧,第一个问题出来了 18 亿条身份信息单表装的下吗?
如果每条身份信息记录平均占用 1KB 的存储空间,那么 18 亿条记录将占用约 1800GB(1.8TB)的磁盘空间。
答: 不一定看系统,好吧实际上我的电脑上的单机 8 版本的 mysql 单表在 14892.42MB (约15G)时候就到上限了,毕竟我这四年的老伙计总过也就 512 G存储你总不能全存我电脑上吧.而阿里那面的建议是单表 10 GB 以内,嗯他肯定有自己的考量,大佬给出的经验值一定是经过计算的,好吧,计算机世界总是有迹可循的,我们大概猜一猜为什么?
好吧,这个问题有人计算过,简单说一下我的看法吧,就是能做到的东西不一定是性价比最高的最合适的,举个简单的例子,五百米的路你打车能到,单车能到,走过去也能到,什么状态下你会打车?当然具体操作看心情,没有完全错误的东西,你怎么做一定有你的理由,理由合理说服自己即可,当然说服自己如果说服不了甲方和用户,当我没说,不想被优化掉.
链接学习资源
RDS MySQL的单表尺寸限制
阿里云官方 Mysql 产品文档
https://help.aliyun.com/zh/rds/support/limits-on-the-size-of-a-single-table-in-apsaradb-rds-for-mysql
在面试时被问到,为什么MySQL数据库数据量大了要进行分库分表?
带图解的完善分库分表讲解,当然我不知道这个用户是不是活跃在我熟悉哪个圈子里的 Yes . 图解加原理讲解看着很明白,嗯比我最开始了解分库分表时候的博客好的多从问题产生到问题解决.当然思维性质的多一些,毕竟很少有人能用实际业务来做讲解嘛,复杂度高了一些,而且实际问题中变数太多,写起来就不太容易了.
https://www.zhihu.com/question/459955079
MySQL查看数据库表容量大小
来源腾讯云,为什么记录它那,主要是那面多线程写满数据之后好奇到底多少数据遇见了单页写入异常问题,所以搜了下嗯怎么看大小,以前没搞过,两三天前才知道能这么玩,我 sql 挺烂的.这篇记录了一些相关查询能更好的让我们知道数据库当前状态.
三种方案优化 2000w 数据大表!忒强~
好吧,这篇文章和我写的也差不多,好吧很强,从各个角度给了下理论值,没有基于尝尽进行实践,不过图文搭配看着蛮不错的.
mysql b+树能存多少条数据?b+树每层有多少分支?
来源 csdn 从 InnoDB 引擎的涉及,数据结构来说明为什么要分库分表,通过计算来讲解嗯不直观,但是有数据结构和基础好一些还是很容易看的懂得,条理清晰得讲明两千万条数据左右为什么要分表提高性能,因为多了 InnoDB 引擎索引不到查询很慢. 看吧经验值其实都是计算加实际经验结合来的,我得实际经验就是,我这台电脑单表数据上限就是 15 g 左右.
http://t.csdnimg.cn/kMMcm
好了回到刚开始得问题,能存吗? 理论可以,但不推荐
如果我 512 的内存,我想保存这些数据怎么办,存不了只能分表在分库,加上我线上服务器和朋友电脑死命存,分表分库,当然我可以加固态,扩展位还是有的,不过服务器的存储空间是大的很多,但是也不推荐存单表,索引不到 2千万后的数据,当然指的是 mysql 的 InnoDB 引擎下,假若嗯真有人为了这个索引,不考虑性能做了索引更多的数据,那也就那样吧,我相信 mysql 的开发团队一定有他的道理,有数据和实际来说话的那种.
分表两种场景下的实践
那么开始分表的实践,当然有可能有人说我是为了这口醋吃了这份饺子,当然确实如此,可能也许明天我就用到了这个场景,可能我这辈子遇不到需要分表的业务,但谁说的准,我记得刚开始整理分布式定时任务,然后一个关系要好的和我同期的开发就遇见了这个场景,小装一波.哈哈哈哈哈.当然下面是来源于星球内部大佬的告诫,嗯等我能写好文章在打广告.
垂直分表
场景讲的就是嗯由用户提供数据, 压缩为 CSV 格式后提供给 AI 分析,提前做好预设,得到分析结论和数据,由 AI 生成可视化代码,清洗 AI 返回数据后入库,前端展示时直接使用.
这里我们选择保存原始用户提供数据,当然我们可以保存 CSV 后的原始数据,当用户可以根据自己的分析查看自己上传数据,可以导出当时上传的数据,或是直接编辑后继续分析.
问题就来了,第一点, AI 能力有限,或者说我们引入第三方数据数据限制,这点是上游的问题,解决不了,但又不是真的解决不了,历史关联消息 + 加更好的预设,实现我传过去多次数据,只有遇见终止符进行输出,嗯没搞错的话这种能力已经有了,或者 ai 压缩提取,嗯已经有人实现了,我们大多数要走的路都是别人走过的路,"为了分表而分表"这种情况大多数前辈可能也走过,emmmm 反正解决办法有了.
第二点,数据库字段上线,关系型数据库的字段存储是有限的 text 类型的存储也不一定能存下所有用户的上传,模拟的时候我就是复制太多数据存不下一个 text 换的更大数据类型,但是使用更大数据类型又可能会存在浪费,嗯 mysql 设计不同的数据类型就是为了优化性能,如果类型不合适反而会本末倒置.
第三点,一个 text 存不下这种情况正常吗? 一张 excel 存的数据有时候大于这个太正常了,虽然有点多,但是你不能不让我存吧,几千条数据放到这个表格里很正常吧,一个村的人口就这些你帮我分析以下相关数据都做不到,好吧,其实嗯还可以拿到数据通过程序计算压缩一波在交给 AI 不过嗯压缩也可以 AI 做,不过暂时不知道人家怎么做的,这里单纯讨论一点,怎么存?
如果放到一个字段会出现什么问题? 首先第一点,如果走索引的话确实会快,但以我的电脑为例,它 1214216 条就到单张表上限了,还在索引范围,但是我再有数据来怎么办,服务器上可能能存储更多数据,但是我记得上次测试别的数据轻松 600 万数据,也就是说单条数据过大确实会影响单张表的数据量,就像是图片视频这种类型本质上也是数据,为啥不能给我存到一条实体里面,也是因为太大了,分儿化之.所以我把用户数据摘除出去单独存储没什么问题,第二点,为什么不单独弄一张表,存 id 对应关系去存,那样本质还是一个字段存储一个超大的内容,本质上讲没区别,当我分出来的表达到上线之后还是要分表的.
怎么存,可以向图片那样选择第三方存储,也可以采用写文件的方式在服务器中生成文件,mysql 数据记录的实体中留下地址,这里使用分表解决,嗯就是讲用户输入看作一张 sql 表存储.好处就是,取用灵活,像是查询 sql 一样查询就好了.
代码实现
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean createChart(CreateChartExcelDTO createChartExcelDTO) {
// 读取数据
List<Map<Integer, String>> list = null;
try {
list = EasyExcel.read(createChartExcelDTO.getMultipartFile().getInputStream())
.excelType(ExcelTypeEnum.XLSX)
.sheet()
.headRowNumber(0)
.doReadSync();
} catch (IOException e) {
log.error("表格处理错误", e);
}
if (CollUtil.isEmpty(list)) {
return false;
}
// 读取表头
LinkedHashMap<Integer, String> headerMap = (LinkedHashMap) list.get(0);
List<String> headerList = headerMap.values().stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList());
StringBuilder createSql = new StringBuilder();
createSql.append("CREATE TABLE ");
String createName = String.format("chart_%s", createChartExcelDTO.getChartId() + " ");
createSql.append(createName);
String format = "VARCHAR(255) NULL DEFAULT NULL";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("id BIGINT(20) AUTO_INCREMENT,");
for (String headerName : headerList) {
stringBuilder.append(headerName).append(" ").append(format).append(",");
}
stringBuilder.append("isDelete tinyint default 0 not null comment '是否删除',");
stringBuilder.append("PRIMARY KEY (`id`)");
createSql.append(" (");
createSql.append(stringBuilder);
createSql.append(");");
String sql = createSql.toString();
Boolean aBoolean = new Boolean(true);
try {
chartMapper.createChartExelByID(sql);
} catch (Exception e) {
aBoolean = false;
throw new RuntimeException(e);
}
if (aBoolean) {
// 读取数据
List<String> dataLines = new ArrayList<>();
for (int i = 1; i < list.size(); i++) {
LinkedHashMap<Integer, String> dataMap = (LinkedHashMap) list.get(i);
List<String> dataList = dataMap.values().stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList());
dataLines.add(StringUtils.join(dataList, ","));
}
// 插入数据
String insertSql = String.format("INSERT INTO %s ", createName);
StringBuilder columnNames = new StringBuilder();
StringBuilder insertValues = new StringBuilder();
boolean isFirstColumn = true;
for (String columnName : headerList) {
if (isFirstColumn) {
columnNames.append(columnName);
isFirstColumn = false;
} else {
columnNames.append(", ").append(columnName);
}
}
insertValues.append(" VALUES ");
for (String line : dataLines) {
StringBuilder values = new StringBuilder();
String[] valuesArray = line.split(",");
for (String value : valuesArray) {
values.append("'").append(value).append("',");
}
values.deleteCharAt(values.length() - 1);
insertValues.append("(").append(values).append("),");
}
insertValues.deleteCharAt(insertValues.length() - 1);
insertSql += "(" + columnNames + ")" + insertValues;
Boolean chartDataInserted = chartMapper.insertChartData(insertSql);
return chartDataInserted;
}
return false;
}
void createChartExelByID(String createSql);
Boolean insertChartData(String insertSql);
List<Map> getOriginalChartById(String getOriginalChart);
<select id="queryChartData" parameterType="string" resultType="map">
${querySql}
</select>
<select id="getOriginalChartById" resultType="java.util.Map">
${getOriginalChart}
</select>
<!-- Boolean createChartExelByID(String createSql);-->
<update id="createChartExelByID">
${createSql}
</update>
<insert id="insertChartData">
${insertChartData}
</insert>
一段又丑又长的代码,大家见笑了,写的时候主要遇见的问题
- 库表不固定,用户数据表头个数不固定 解决方案循环,长度不固定,解决方案固定长度,当然啊,给的不太大本来想和 excel 单个单元可输入数据相同来着,但是没找到哪个最多能存多大 ,当然不是问题,不行我给text 也行, mysql 单表最多列上限是 1024 ,这个只能校验的时候解决了,拒绝1024列以上的 excel ,实现方式,可以 xml 动态拼接,然后用循环标签循环内容,但是我觉得到哪里我不太容易掌控框架内的结构,就硬拼的.
- 心态,啊以上工作是很简单也很繁琐的东西,嗯其中还留了个表头存储在分出来之前的哪个表上存的json,这样的话可以快速查询,方式也很简单,内存计算就可以确定查那个了.
- 输入内容不可控性,啊比如各种打乱我拼接的符号,解决方案为过滤时候校验,要么统一字符替换,反正很麻烦,嗯这块就简单替换掉了,啊啊啊啊啊啊,sql 注入啊 也算是用黑名单解决的吧.
- 测试没全面覆盖.
这样就实现了用户上传一个 excel 生成一张表存贮数据,当然这玩意也不能无节制,不过配合 AIGC 的限流和配额也是限制了表的增长速度,同时可以将是否存储数据作为可选项 vip 功能嘛.
至此,第一种分表完全实现了,就是一条主表数据,对应一张或零张表,当然要写作两张表互相关联的分表也可以,只是这里不太合适.
水平分表
代码实现
API安全之路:从黑客攻防到签名认证体系
https://blog.csdn.net/qq_47923467/article/details/133470310?spm=1001.2014.3001.5502
这是在另一个场景下的分表,当然,这个分表为了分而分的意味更重一些,在另一个笔记中有详细业务实现,当然如果不愿意翻简单说一说,这种就是嗯,结构固定,在这个场景下遇见的问题就是表是固定的,以两种方式实现了,还可以文件存储建表 sql 查出来为模板更新,当然都差不多思想,直接写死肯定会快一些,每次从外部查如果更改起来灵活些,不过在这个场景下确实没必要,因为数据量注定大不到哪里.为了吃醋包饺子,当然分库分表还有更高深的玩法,但是我遇不到,就是为了学习罢了.
<insert id="addInterfaceTable" parameterType="long">
create table if not exists user_interface_info_${interfaceId}
(
id bigint auto_increment comment '主键'
primary key,
userId bigint not null comment '调用用户 id',
totalNum int default 0 not null comment '总调用次数',
leftNum int default 0 not null comment '剩余调用次数',
status int default 0 not null comment '0-正常,1-禁用',
accessKey varchar(512) not null comment 'accessKey',
secretKey varchar(512) not null comment 'secretKey',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除(0-未删, 1-已删)'
)
comment '用户调用接口关系_${interfaceId}';
</insert>
<select id="getInterfaceTable" parameterType="long" resultType="string">
SHOW TABLES LIKE 'user_interface_info_${interfaceId}';
</select>
数据库内新建模板库
create table user_interface_info_template
(
id bigint auto_increment comment '主键'
primary key,
userId bigint not null comment '调用用户 id',
totalNum int default 0 not null comment '总调用次数',
leftNum int default 0 not null comment '剩余调用次数',
status int default 0 not null comment '0-正常,1-禁用',
accessKey varchar(512) not null comment 'accessKey',
secretKey varchar(512) not null comment 'secretKey',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
isDelete tinyint default 0 not null comment '是否删除(0-未删, 1-已删)'
)comment '用户调用接口关系_template';
<select id="getUserInterfaceInfoTemplate" resultType="map">
SHOW CREATE TABLE user_interface_info_template
</select>
<insert id="addInterfaceTables" parameterType="string">
${interfaceTables}
</insert>
测试
@Test
void testCreatSQL(){
Map<String, String> interfaceInfoTemplate = interfaceInfoMapper.getUserInterfaceInfoTemplate();
System.out.println(interfaceInfoTemplate.toString()+"张三");
String value = interfaceInfoTemplate.get("Create Table");
String template = value.replace("template", "123456");
interfaceInfoMapper.addInterfaceTables(template);
System.out.println("张三");
}
测试部分
@PostMapping("/insertChat")
public boolean insertChat() throws IOException {
List<List<Chart>> futures = new ArrayList<>();
StopWatch totalDuration = new StopWatch();
StopWatch buildInputDuration = new StopWatch();
totalDuration.start();
ArrayList<CompletableFuture<Void>> completableFutures = new ArrayList<>();
buildInputDuration.start();
for (int i = 0; i < 20; i++) {
List<Chart> charts = new LinkedList<>();
for (int j = 0; j < 5000; j++) {
Chart chart = Chart.builder()
.goal("分析店铺内商品" + j)
.name("商品分析" + j)
.chartData("商品名称,商品简介,商品分类,价格,库存,销量,浏览量\n" +
"123,1232,张三,¥0.00,0,0,48\n" +
"Iphone 18 Pro Max,This is Iphone 18 Pro Max,手机,¥12333.00,3,6,138\n" +
"商品名称,商品简介\n" +
"Why do we use it?\n" +
"It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as oppose,上装,¥130.00,9,1,300\n" +
"旅行鸭硅胶可爱钥匙链鸭子公仔汽车钥匙挂件卡通小饰品挂饰钥匙扣 奶茶包旅行鸭蓝色,旅行鸭硅胶可爱钥匙链鸭子公仔汽车钥匙挂件卡通小饰品挂饰钥匙扣 奶茶包旅行鸭蓝色,张三,¥15.00,682,98,34227\n" +
"WeighMax空气炸锅家用小型大容量多功能可视无油电炸锅机智能煎炸烤焙大功率定时定温炸鸡薯条机 触控式小溦粉,WeighMax空气炸锅家用小型大容量多功能可视无油电炸锅机智能煎炸烤焙大功率定时定温炸鸡薯条机 触控式小溦粉,清凉一夏,¥129.00,201,67,12547\n" +
"小米电视EA55 2022款 55英寸 金属全面屏 远场语音 逐台校准4K超高清智能教育电视机L55M7-EA,小米电视EA55 2022款 55英寸 金属全面屏 远场语音 逐台校准4K超高清智能教育电视机L55M7-EA,手机,平板电脑,日用杂货,¥2299.00,2527,73,15163\n" +
"小米 MIX4 骁龙888+ 一体化陶瓷机身 全面屏 一亿像素三摄 8GB+128GB 陶瓷白 5G旗舰手机 保值换新版,【简介】小米 MIX4 骁龙888+ 一体化陶瓷机身 全面屏 一亿像素三摄 8GB+128GB 陶瓷白 5G旗舰手机 保值换新版,张三,¥5098.00,45830,170,270637\n" +
"男鞋AF款篮球鞋减震耐磨百搭板鞋男女冰淇淋小白鞋低帮女运动鞋 6222白色 131,男鞋AF款篮球鞋减震耐磨百搭板鞋男女冰淇淋小白鞋低帮女运动鞋 6222白色 131,张三,¥158.88,9642,134,7273")
.chartType("折线图")
.status("succeed")
.userId(1652457118348935170L)
.build();
charts.add(chart);
}
log.info("构建数据时长: " + buildInputDuration.getTime());
buildInputDuration.suspend();
buildInputDuration.resume();
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
try {
System.out.println("threadName: " + Thread.currentThread().getName());
StopWatch singleBatchInsertionTime = new StopWatch();
singleBatchInsertionTime.start();
chartService.saveBatch(charts, 200);
singleBatchInsertionTime.stop();
log.info("单批次插入时长: " + singleBatchInsertionTime.getTime());
} catch (Exception e) {
// 处理异常,可以记录到日志文件中
log.error("插入数据发生异常: " + e.getMessage());
}
}, threadPoolExecutor);
completableFutures.add(voidCompletableFuture);
}
try {
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[]{})).join();
} finally {
// 关闭线程池
threadPoolExecutor.shutdown();
}
totalDuration.stop();
log.info("插入 数据总时长: " + totalDuration.getTime());
return true;
}
看着不多是吧.
"男鞋AF款篮球鞋减震耐磨百搭板鞋男女冰淇淋小白鞋低帮女运动鞋 6222白色 131,男鞋AF款篮球鞋减震耐磨百搭板鞋男女冰淇淋小白鞋低帮女运动鞋 6222白色 131,张三,¥158.88,9642,134,7273"
这条数据我写到了 text 的极限,这块想要高的话可以设置定时任务吃饭去,当然这还有一个坑,线程这方面,啊这玩意也是机器不同体质不同,实际操作起来真是慢慢调参数吧,不一定线程越多越快,也不一定经验值在我的电脑上有用,啊总结就是体质不同
是把嘿嘿嘿.单表最大上线哪个 ERROR 没有截下来,也许我一生就见到它一次也说不定,我希望下次见到它是意外的惊喜.
InnoDB 存储引擎:
InnoDB 存储引擎支持 64TB 的数据文件大小(文件扩展)。
单表的大小理论上可以接近 64TB,但实际操作时应根据硬件、系统和其他因素留有一定的余地。
MyISAM 存储引擎:
MyISAM 存储引擎支持 256TB 的数据文件大小。
单表的大小理论上可以接近 256TB,但实际操作时应根据硬件、系统和其他因素留有一定的余地。
Memory 存储引擎:
Memory 存储引擎将表存储在内存中,受到可用内存的限制。单表大小可接近实际可用内存。
操作系统和文件系统:
操作系统和文件系统也会对单表大小有影响。一些文件系统可能会限制单个文件的大小。
硬件和配置:
硬件性能(如内存、CPU)也会影响单表的最大大小。
分区表:
MySQL 支持分区表,可以将单个逻辑表分割成多个物理存储区域。这样可以克服单表大小的限制。
请注意,这些是一般性的估算。具体的情况可能会因为你的数据库的配置和环境不同而不同。在实际应用中,建议在接近这些上限之前进行性能测试和容量规划,以确保系统的稳定性和性能。
单表数据极限估算方式
行大小:首先,你需要了解表的每一行占用的存储空间。这包括所有列和索引的存储空间。例如,如果一行有 10 个列,每列都是整数,那么这 10 个整数将占用 40 个字节(4 个字节每个整数)。
估算平均行大小:通过取样几行数据,计算出平均行大小。
总行数:查询表的行数,可以使用 SELECT COUNT(*) FROM your_table; 查询语句。
数据大小:将平均行大小乘以总行数,得到表内数据的总大小。
索引大小:如果表有索引,需要将每个索引的大小相加起来。
碎片和空间浪费:一些存储引擎可能会有一些碎片或者由于数据的增删改而产生的空间浪费。
数据库性能统计
ANALYZE TABLE chart;
数据统计语法
select
table_schema as '数据库',
table_name as '表名',
table_rows as '记录数',
truncate(data_length/1024/1024, 2) as '数据容量(MB)',
truncate(index_length/1024/1024, 2) as '索引容量(MB)'
from information_schema.tables
order by data_length desc, index_length desc;
截图一是在我去除掉 csvData 数据量大的列后,重新插入的数据,可以看到快 8 千万的单条数据量,而在另一张图种,单条数据在一百多万的时候就已经到达了上限,差别很大吧,当然从大小上看,反而是百万的到达极限存储数据比较多,嗯应该是索引也占用存储空间了.
到达单表存储上限触发 bug
拆分和未拆封对比
以拆分存储原始数据后,单表存储约八千万算,能支持存储八千万张表
这个错误是因为多线程线程数大于链接池错误造成的.
以上,我们只考虑了存储,并没有考虑性能,只是告诉大家能做到,但是并没有考虑性能和并发的情况,按照上面某一篇关于计算 b+ 数索引的文章来算,如果数据不在索引范围内,性能就会产生很大的变动,实际测试的坑后期会踩一踩.验证为什么要把单表数据压制在 2千万左右.
当然,以上只写了分,和简单的查,不过这些实现都可以通过动态 sql 去实现,实现起来还算是蛮容易的,当需要分库分表的业务并没有太多的时候简单的实现可以带来更高效的开发效率,也能降低运维成本,不过如果是复杂场景下的分库分表,就需要完整成熟的消息中间件了,学习开发成本都会高很多,不过嗯如果加一张表记录整个表分表结构与逻辑,实现删除表,查询表内状态也算是实现了一个小型的分表管理.
mycat 分库分表消息中间件,我看人家做的挺牛逼的,而且培训机构一般讲分库分表都用这个,不过也看情况嘛,简单分库分表有更简单的成本更低的解决办法,适合的才是最好的.
SpringBoot多数据源以及事务处理
坑
- 大数据,数仓更大数据量的存储
- 分库分表其他实践
- 多线程开发深入学习
- 写好sql ,数据库的原理深入学习
- 多数据源开发
- 实际测试数值说话,提升多少会有什么效果
- 完善项目
- 啊,最近可能没时间填坑有别的事情要去做,工作外,别的时间可能都要给它了.