执迷 执迷
首页
  • 技术分享

    • 小白都能看懂的闭包
    • GO-GMP模型
    • MySQL调优
    • centos7部署nacos
    • elasticserch
    • redis
  • 发布一个node插件

    • 发布一个npm包
    • 如何打包一个插件工具库
    • 打包工具的选择
  • JavaScript
  • ES6
  • CSS
  • 框架
  • Node
  • 服务
  • 其他
自我介绍
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

执迷

代码也是艺术
首页
  • 技术分享

    • 小白都能看懂的闭包
    • GO-GMP模型
    • MySQL调优
    • centos7部署nacos
    • elasticserch
    • redis
  • 发布一个node插件

    • 发布一个npm包
    • 如何打包一个插件工具库
    • 打包工具的选择
  • JavaScript
  • ES6
  • CSS
  • 框架
  • Node
  • 服务
  • 其他
自我介绍
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 前端

  • 后端及运维

    • 服务
    • GMP模型
    • MySQL调优
    • centos7部署nacos
    • consul单台机器部署,注册外网服务健康检查
    • centos7安装ES
    • MySQL底层
    • centos7安装rabbitmq
    • docker部署redis
    • 平常常用命令
    • MySQL调优
      • 数据表单表能支持10亿吗
      • 如何高效的写入数据库
      • 要不要分库
      • 怎么保证写入的数据有序
      • 怎么协调文件任务和写数据库任务
      • 如何保证任务的可靠性
      • 如何协调读取任务的并发度
      • 设计最小任务调度模型--golang实现
  • 年终总结

  • 个人博客
  • 后端及运维
刘某
2024-08-04
目录

MySQL调优原创

# 10亿条数据如何快速插入MySQL

  • 单条数据1kb
  • 10亿数据不是一个大文件,而是若干个文件
  • 有序导入

# 数据表单表能支持10亿吗

答案是不能,单表推荐是2000w以下,MySQL索引数据结构是B+树,全量数据存储在主键索引,也就是聚簇索引的叶子结点上,B+树插入和查询的性能和B+树层数直接相关,2000W以下是3层索引,而2000w以上则可能为四层索引

Mysql b+索引的叶子节点每页大小16K。当前每条数据正好1K,所以简单理解为每个叶子节点存储16条数据。b+索引每个非叶子节点大小也是16K,但是其只需要存储主键和指向叶子节点的指针,我们假设主键的类型是 BigInt,长度为 8 字节,而指针大小在 InnoDB 中设置为 6 字节,这样一共 14 字节,这样一个非叶子节点可以存储 16 * 1024/14=1170。

层数 最大数据量
2 1170 * 16 = 18720
3 1170 * 1170 * 16 = 21902400 = 2000w
4 1170 * 1170 * 1170 * 16 = 25625808000 = 256亿

# 如何高效的写入数据库

相比较于单条,批量写入性能更高,每次写入的数量根据机器的性能决定

批量写入数据如何保证成功? 使用事务,保证同时成功、同时失败

事务失败需要重试,当重试了N次后,可以考虑单条或者减少批次的数量查看原因

写入数据库时不要建任何辅助索引,索引排序有较大的消耗

# 要不要分库

mysql 单库的并发写入是有性能瓶颈的

数据使用SSD存储,性能会更好,如果是HDD,顺序读写性能会更好,但是并发支持差,单磁头需要反复寻道写入,耗时大大增加;SSD厂商不同,性能也表现不同,没有测试的情况下,其实并不知道实际的性能

所以在设计上要更加灵活,需要支持以下能力

  • 支持配置数据库的数量
  • 支持配置并发写表的数量,(如果MySQL是HDD磁盘,只让一张表顺序写入,其他任务等待)

# 怎么保证写入的数据有序

使用自定义的主键

根据文件后缀,如

  1. index_90.txt 被写入 数据库database_9,table_0 ,
  2. index_67.txt被写入数据库 database_6,table_7。

并发执行,配合任务id taskID

# 怎么协调文件任务和写数据库任务

读取文件任务与写入数据任务分离,假设100个读取任务,每个任务读取一批数据,立即写入数据库是否可以呢?机器由于有瓶颈,无法满足1个库同时并发大批量写入10个表,所以100个任务同时写入数据库,势必导致每个库同时有10个表同时在顺序写,这加剧了磁盘的并发写压力。为尽可能提高速度,减少磁盘并发写入带来的性能下降, 需要一部分写入任务被暂停的。那么读取任务需要限制并发度吗?不需要。

假设写入任务和读取任务合并,会影响读取任务并发度。初步计划读取任务和写入任务各自处理,谁也不耽误谁。但实际设计时发现这个方案较为困难。

最初的设想是引入Kafka,即100个读取任务把数据投递到Kafka,由写入任务消费kafka写入DB。100个读取任务把消息投递到Kafka,此时顺序就被打乱了,如何保证有序写入数据库呢?我想到可以使用Kafka partition路由,即读取任务id把同一任务的消息都路由到同一个partition,保证每个partition内有序消费。

要准备多少个分片呢?100个很明显太多,如果partition小于100个,例如10个。那么势必存在多个任务的消息混合在一起。如果同一个库的多个表在一个Kafka partition,且这个数据库只支持单表批量写入,不支持并发写多个表。这个库多个表的消息混在一个分片中,由于并发度的限制,不支持写入的表对应的消息只能被丢弃。所以这个方案既复杂,又难以实现。

所以最终放弃了Kafka方案,也暂时放弃了将读取和写入任务分离的方案。

最终方案简化为 读取任务读一批数据,写入一批。即任务既负责读文件、又负责插入数据库。

# 如何保证任务的可靠性

如果读取任务进行到一半,宕机或者服务发布如何处理呢?或者数据库故障,一直写入失败,任务被暂时终止,如何保证任务再次拉起时,再断点处继续处理,不会存在重复写入呢?

刚才我们提到可以 为每一个记录设置一个主键Id,即 文件后缀index+文件所在行号。可以通过主键id的方式保证写入的幂等。

所以也无需数据库自增主键ID,可以在批量插入时指定主键ID。

如果另一个任务也需要导入数据库呢?如何实现主键ID隔离,所以主键ID还是需要拼接taskId。例如{taskId}{fileIndex}{fileRowNumber} 转化为Long类型。

如果taskId较大,拼接后的数值过大,转化为Long类型可能出错。

最重要的是,如果有的任务写入1kw,有的其他任务写入100W,使用Long类型无法获知每个占位符的长度,存在冲突的可能性。而如果拼接字符串{taskId}{fileIndex}{fileRowNumber} ,新增唯一索引,会导致插入性能更差,无法满足最快导入数据的诉求。所以需要想另一个方案。

可以考虑使用Redis记录当前任务的进度。例如Redis记录task的进度,批量写入数据库成功后,更新 task进度。

INCRBY KEY_NAME INCR_AMOUNT
1

指定当前进度增加100,例如 incrby task_offset_{taskId} 100。如果出现批量插入失败的,则重试插入。多次失败,则单个插入,单个更新redis。要确保Redis更新成功,可以在Redis更新时 也加上重试。

如果还不放心Redis进度和数据库更新的一致性,可以考虑 消费 数据库binlog,每一条记录新增则redis +1 。

如果任务出现中断,则首先查询任务的offset。然后读取文件到指定的offset继续 处理。

# 如何协调读取任务的并发度

为了避免单个库插入表的并发度过高,影响数据库性能。可以考虑限制并发度。如何做到呢?

既然读取任务和写入任务合并一起。那么就需要同时限制读取任务。即每次只挑选一批读取写入任务执行。

# 设计最小任务调度模型--golang实现

小型调度模型,不考虑中间件

  • 任务队列
  • 已就绪处理单元
  • 重试队列

初始化任务队列,以及初始化处理单元列表,两个数据结构都是可扩容的列表,每一个处理单元都由一个协程构成,

#数据库#MySQL
平常常用命令
2022年终总结

← 平常常用命令 2022年终总结→

最近更新
01
MySQL底层 原创
07-30
02
consul单台机器部署,注册外网服务健康检查 原创
07-30
03
centos7安装ES 原创
07-30
更多文章>
Theme by Vdoing | Copyright © 2019-2024 执迷 | 闽ICP备2022018045号 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式