如何优化业务单表读写缓慢

本文详细探讨了MySQL数据库在电商系统中的分库分表策略,涉及存储选型(关系型、NoSQL与NewSQL)、分片键选择、分片策略(范围分片、Hash取模等)、业务代码调整、历史数据迁移及未来扩容方案,同时揭示了分表分库的不足与应对策略。
导读关系型数据库目前市面上主流无非三种:MySQL、Oracle、SqlServer,笔者更倾向于MySQL,也是很多新型企业在用的一种数据库,因此本篇文章也将重点围绕MySQL展开。

在前面的文章中探讨了架构优化的两种方案:冷热分离、查询分离。

  • 冷热分离
  • 查询分离

查询分离其实就是利用了非关系数据库的高性能,但是不足之处也很明显:当主数据量越来越多,写操作缓慢;这种问题如何破局?可见任何一种优化方案都不是最终的银弹,只有不断的优化演变

这篇文章就来介绍一下解决方案:分库分表,将围绕以下几点介绍:

  • 拆分后的存储选型?
  • 分库分表的实现思路?
  • 分库分表的不足?

拆分后的存储选型?

在介绍选型之前先来介绍下架构背景,笔者曾经做过电商系统的优化,该系统中包含的两个主体:

  • 用户:数据量上千万,每日增长10W+
  • 订单:数据量上亿,每日百万级的增长

对于如此量级的数据,单库单表的情况下,无论是IO还是CPU都扛不住,架构上的优化是必然。

经过了多次探讨尝试,最终选择了分库分表。

说到分库分表首先想到的就是存储选型,关于持久层的选型主流的无非有如下几种:

  • 关系型数据库:MySQL、Oracle.........
  • NoSQL:MongoDB、ES......
  • NewSQL:TiDB........

1. 关系型数据库

关系型数据库目前市面上主流无非三种:MySQL、Oracle、SqlServer,笔者更倾向于MySQL,也是很多新型企业在用的一种数据库,因此本篇文章也将重点围绕MySQL展开

在任何系统中关系型数据库的地位都是不可或缺的,它的强约束性、事务的控制、SQL语法、锁....这些功能可谓是久经考验,因此在功能上 MySQL 能满足我们所有的业务需求。

2. NoSQL

说到NoSQL,第一个想到就是MongoDB ,它的分片功能从并发性和数据量这两个角度已经能满足一般大数据量的需求,但是仍然需要考虑如下几点:

约束性:MongoDB 不是关系型数据库而是文档型数据库,它的每一行记录都是一个结构灵活可变的 JSON,比如存储非常重要的订单数据时,我们就不能使用 MongoDB,因为订单数据必须使用强约束的关系型数据库进行存储。
业务功能考量:事务的控制、SQL语法、锁以及各种千奇百怪的SQL在已有的架构上都曾久经考验,但是MongoDB在这些功能需要上并不能满足
业务改造考量:未拆分前使用关系型数据库,使用NoSQL之后对于SQL的改造比较麻烦,项目周期更长

3. NewSQL

NewSQL目前比较主流则是TiDB,该技术比较新,虽然能够满足大数据量的存储,但是在选择上还是需要做些考量:

熟悉程度考量:如果你所在公司架构组对于NewSQL比较数据或者已经有在使用,则可以选择

稳定性考量:关系型数据毕竟是久经考验,在稳定性方面肯定是比较好,但是NewSQL的稳定性却无法去考量,建议初期阶段可以将一些不太重要的数据使用NewSQL存储

基于MySQL的分库分表

什么是分表分库?分表是将一份大的表数据拆分存放至多个结构一样的拆分表;分库就是将一个大的数据库拆分成多个结构一样的小库。

前面介绍的三种拆分存储技术,在我以往的项目中我都没使用过,而是选择了基于 MySQL 的分表分库,主要是有一个重要考量:分表分库对于第三方依赖较少,业务逻辑灵活可控,它本身并不需要非常复杂的底层处理,也不需要重新做数据库,只是根据不同逻辑使用不同 SQL 语句和数据源而已。

目前市面上主流的分库分表分为两种模式:Proxy模式、Client模式

Proxy模式属于业务无侵入型,直接代理数据库,对于开发者一切都是无感知的,SQL 组合、数据库路由、执行结果合并等功能全部存放在一个代理服务中,比如MyCat、ShardingSphere都对Proxy模式提供了支持

Client模式属于业务侵入型,将分库分表的逻辑放在客户端,客户端需要引入一个jar,比如Sharding-JDBC,架构图如下:

市面上对于分库分表中间件如下:

两种模式的优缺点也很明显:

  • Proxy模式:资源解耦,业务无侵入;缺点则是运维成本相对较高
  • Client模式:代码灵活控制,运维成本低;缺点则是语言限制,升级不方便

分库分表的实现思路

在落实分表分库解决方案时,我们需要考虑 5 个要点。

1. 分片键如何选择?

针对订单这个业务,其中涉及到以下几个主要的字段:

  • user_id:用户id
  • order_id:订单id
  • order_time:下单时间
  • store_id:店铺id

经过考量,最终选择了user_id作为ShardingKey,为什么呢?

选择user_id作为ShardingKey需要结合业务场景,订单系统中常见的业务:

  • C端用户需要查询所有的订单(user_id)
  • 后台需要根据城市查询所有订单(user_city_id)
  • B端商家需要统计自己店铺的下单量(store_id)

以上三种业务场景,判断下优先级,C端用户肯定是需要优先满足,因此使用user_id作为ShardingKey

这样在查询时需要将user_id传递过来才能定位到指定库、表

选择字段作为分片键时,我们一般需要考虑三点要求:数据尽量均匀分布在不同表或库、跨库查询操作尽可能少、这个字段的值不会变(这点尤为重要)。

2. 分片的策略是什么?

选择user_id作为ShardingKey之后,需要考虑使用分片策略了,主要分为如下三种

范围分片
假如user_id是自增的数字,则可以根据user_id范围进行分片,每100万份分为一个库,每10万份分为一个表,此时单个库中将分为10张表,如下表:

  • Hash取模

这种方案是根据Hash值进行分片,比如Hash函数为:hash(user_id%8),这里是将user_id对8这个特定值取模,最终分为了8张表;这里一般为了方便后续扩容,建议选择2的N次方

  • 范围分片和Hash取模混合

比如先按照范围对user_id拆分,每100万份分为一个库,在对这100万份数据进行Hash取模(hash(user_id%8))拆分成8个表

当然以上三种方案的优缺点也是非常明显,这里不再赘述了

需要注意的是:在拆分之前,为了避免频繁的扩容,一定要对未来5年或者10年数据增长做个判断,预留更多的分片

3. 业务代码如何修改

业务代码的修改这里就不好说了,和自身的业务是强关联。

但是,在这里我想分享一些个人观点。近年来,分表分库操作愈发容易,不过我们需要注意几个要点。

我们已经习惯微服务了,对于特定表的分表分库,其影响面只在该表所在的服务中,如果是一个单体架构的应用做分表分库,那真是伤脑筋。

在互联网架构中,我们基本不使用外键约束。

随着查询分离的流行,后台系统中有很多操作需要跨库查询,导致系统性能非常差,这时分表分库一般会结合查询分离一起操作:先将所有数据在 ES 索引一份,再使用 ES 在后台直接查询数据。如果订单详情数据量很大,还有一个常见做法,即先在 ES 中存储索引字段(作为查询条件的字段),再将详情数据存放在 HBase 中(这个方案我们就不展开了)。

4. 历史数据迁移?

历史数据的迁移非常耗时,有时迁移几天几夜都很正常。而在互联网行业中,别说几天几夜了,就连停机几分钟业务都无法接受,这就要求我们给出一个无缝迁移的解决方案。

还记得讲解查询分离时,我们说过的方案吗?我们再来回顾下,如下图所示:

历史数据迁移时,我们就是采用类似的方案进行历史数据迁移,如下图所示:

此数据迁移方案的基本思路:存量数据直接迁移,增量数据监听 binlog,然后通过 canal 通知迁移程序搬运数据,新的数据库拥有全量数据,且校验通过后逐步切换流量。

数据迁移解决方案详细的步骤如下:

  • 上线 canal,通过 canal 触发增量数据的迁移;
  • 迁移数据脚本测试通过后,将老数据迁移到新的分表分库中;
  • 注意迁移增量数据与迁移老数据的时间差,确保全部数据都被迁移过去,无遗漏;
  • 第二步、第三步都运行完后,新的分表分库中已经拥有全量数据了,这时我们可以运行数据验证的程序,确保所有数据都存放在新数据库中;
  • 到这步数据迁移就算完成了,之后就是新版本代码上线了,至于是灰度上还是直接上,需要根据你们的实际情况决定,回滚方案也是一样。

5. 未来的扩容方案是什么?

随着业务的发展,如果原来的分片设计已经无法满足日益增长的数据需求,我们就需要考虑扩容了,扩容方案主要依赖以下两点。

分片策略是否可以让新表数据的迁移源只是 1 个旧表,而不是多个旧表,这就是前面我们建议使用 2 的 N 次方分表的原因;
数据迁移:我们需要把旧分片的数据迁移到新的分片上,这个方案与上面提及的历史数据迁移一样,我们就不重复啰唆了。

分表分库的不足

分表分库的解决方案讲完了,以上就是业界常用的一些做法,不过此方案仍然存在不足之处。

增量数据迁移:如何保证数据的一致性及高可用性
短时订单量大爆发:分表分库仍然扛不住时解决方案是什么?

<think> 首先,用户的问题是:"我想查找解决SQL数据上传数据缓慢问题的方法"。用户提供了几个引用,我需要基于这些引用来回答。引用来自站内文章,我需要参考它们来给出解决方案。 回顾引用内容: - 引用[1]: 讨论了MySQL单表千万数据插入速度过慢的解决方法。包括分库/分/分区。问题场景是数据上传接口timeout,因为单表数据达到千万级别,unique id导致插入慢。建议:使用批量插入(但InnoDB中出错会回滚),分或分库(需要改代码),分区(不用改代码)。分区操作包括比较各种分区的不同点。[^1] - 引用[2]: 提到在Pandas中使用to_sql()方法写入大量数据可能不高效,建议使用COPY方法。[^2] - 引用[3]: 展示了Hive中的SQL代码,涉及创建分区和插入数据。[^3] - 引用[4]: 关于数据库备份工具,提到备份小工具、多线程机制、数据库连接池等。[^4] 用户的问题是针对SQL数据上传数据缓慢的问题,所以我需要聚焦于如何优化数据插入速度。 关键点从引用中提取: - 从引用[1]: 批量插入可以提高速度(但要注意事务回滚),分区可以优化查询和插入性能。 - 从引用[2]: 对于大量数据写入,使用COPY方法比to_sql()更高效。 - 引用[3]展示了分区的用法,这可以与分区优化相关。 - 引用[4]涉及备份工具,虽然相关但不是直接针对上传数据缓慢,或许可以提到并行处理。 用户说:"上述引用仅供参考,并不是用户真正的需求",意思是这些引用是上下文,但我的回答应该基于用户的问题。用户上一次的问题是"我想查找解决SQL数据上传数据缓慢问题的方法",所以我需要直接回答这个。 系统级指令要求: - 回答中引用的段落末尾自然地添加引用标识,例如:[^1] - 使用中文回答 - 生成相关问题 - LaTeX语法正确(虽然用户问题中没有数学内容,但如果有需要,应该使用) - 结构清晰,帮助用户逐步解决问题 回答结构: 1. 理解问题:简要解释数据上传缓慢的原因。 2. 解决方法:基于引用提供多种解决方案。 3. 逐步指导:如何实施这些方法。 4. 生成相关问题。 现在,组织内容: - **原因分析**:单表数据量过大(如千万级)、唯一索引导致插入慢、事务处理效率等。[基于引用[1]] - **解决方法**: - **批量插入**:提高插入速度,但注意事务原子性(InnoDB回滚问题)。建议使用批量插入语句。[^1] - **分区**:将分成多个分区,提高IO效率,便于维护。[^1][^3] - **优化插入方法**:使用COPY命令或类似工具进行高效大批量数据加载。[^2] - **分或分库**:如果数据量巨大,考虑分或分库,但需要代码修改。[^1] - **并行处理**:引入多线程机制和数据库连接池。[^4] - **实施步骤**:例如,如何使用分区;如何设置批量插入;如何使用COPY方法。 生成相关问题:基于主题,生成3-5个相关问题。 确保回答真实可靠:基于提供的引用,不要发明新的东西。 添加引用标识:每当我提到引用内容时,在末尾添加如[^1]。 最终回答格式: - 以中文书写。 - 结构清晰:分步骤。 - 结束时生成相关问题。 提纲: 1. **问题分析**:解释为什么SQL数据上传数据会缓慢,原因包括单表数据量大、索引维护、事务处理等。 2. **解决方法综述**:列出主要方法。 3. **详细解决方案**: - 方法1: 使用批量插入 - 如何操作,优缺点。 - 方法2: 应用分区策略 - 如何创建分区,实施步骤。 - 方法3: 采用高效数据加载工具如COPY命令。 - 方法4: 考虑分或分库。 - 其他提示:如优化索引、使用并行处理。 4. **总结建议**:根据场景推荐最佳方案。 5. **相关问题**:生成3个相关问题。 </think> ### 解决SQL数据上传数据缓慢问题的方法 根据您的查询,结合站内引用和我对数据库优化的理解,我将系统地解释解决SQL数据上传数据缓慢问题的原因和方法。上传数据缓慢通常源于单表数据量过大(如千万级别)、索引维护开销高、事务处理效率或插入方式不合理等因素[^1]。以下是针对这些问题的详细解决方案,基于最佳实践和引用内容进行优化建议。每个方法都力求可靠,并附上实施步骤。 #### 1. **问题原因分析** 上传数据缓慢常见原因包括: - **单表数据量过大**:当数据达到千万级别时,插入操作会因磁盘IO和索引更新而变慢。例如,引用[1]中提到,历史数据积累导致单表过大,unique id索引增加了插入开销[^1]。 - **索引和维护开销**:唯一索引(如unique id)在每次插入时需校验唯一性,消耗额外资源[^1]。 - **插入方式效**:逐行插入(而非批量)效率下;在高并发下,事务管理(如InnoDB的回滚机制)可能拖累性能[^1][^2]。 - **数据库设计缺陷**:缺乏分区或分机制,导致数据集中存储,影响读写速度[^1][^3]。 接下来,我推荐几种高效解决方法,从易实施到高成本依次排序。 #### 2. **解决方法与实施步骤** ##### 方法1: **使用批量插入替代逐行插入** 这是最易实现的优化手段,能显著提升速度(引用[1]明可提高10倍以上),尤其适合中小规模数据上传。 - **原理**:批量插入减少数据库连接次数和事务开销。例如,一次插入多条记录而不是逐条提交。 - **实施步骤**: 1. **修改插入语句**:SQL中使用`INSERT INTO table (col1, col2) VALUES (val1, val2), (val3, val4), ...`格式批量插入。在代码中用数组或集合传递数据。 2. **处理事务风险**: - InnoDB引擎下,批量插入失败会导致全体回滚(如引用[1]所述)。建议先做数据校验,或在代码中生成唯一值(如UUID)作为临时索引,减少数据库唯一校验负担[^1]。 - MyISAM引擎支持部分成功插入(出错前记录保留),但多数现代应用使用InnoDB,需权衡一致性与速度。 3. **测试与优化**:从100条/批开始测试,逐步增加批次大小。引用[2]建议在Python中使用Pandas的`to_sql()`配合`method='multi'`参数实现批量插入,或改用COPY命令提升效率[^2]。 - **优点**:代码改动小,见效快。 - **缺点**:事务回滚风险;数据量超大时(如亿级)提升有限。 - **最佳场景**:数据上传量在百万级以内,且能容忍部分延迟校验[^1]。 ##### 方法2: **应用分区策略** 分区将大拆分为多个物理文件,分散IO压力(如引用[1]和[3]所述),无需大改代码即可优化插入和查询速度。 - **原理**:基于时间、范围或hash分区(如按月份或手机尾号),使数据分散存储,减少文件负载。 - **实施步骤**: 1. **选择分区类型**:引用[1]建议比较分区类型: - **范围分区(RANGE)**:适合时间序列数据(如按日期分区),如引用[3]中的`PARTITIONED BY (pt STRING)`将数据按分区存储[^3]。 - **列分区(LIST)**:按离散值分区(如手机尾号)。 - **哈希分区(HASH)**:均匀分布数据,适合随机访问。 2. **创建分区**:在SQL中执行类似以下代码(基于引用[3]示例): ```sql -- 创建分区(MySQL示例) CREATE TABLE user_data ( user_id BIGINT, user_name VARCHAR(255), create_time DATE ) PARTITION BY RANGE (YEAR(create_time)) ( PARTITION p0 VALUES LESS THAN (2020), PARTITION p1 VALUES LESS THAN (2021), PARTITION p2 VALUES LESS THAN (2022) ); ``` 3. **插入数据**:插入时自动路由到对应分区。引用[3]展示如何使用`INSERT OVERWRITE TABLE ... PARTITION`高效加载数据[^3]。 4. **维护分区**:定期添加新分区(如每月),删除旧分区提升删除速度(如引用[1]所述)[^1]。 - **优点**:性能提升明显(尤其IO密集型操作),历史数据删除快,代码改动小。 - **缺点**:需SQL权限操作结构,查询需包含分区键。 - **最佳场景**:数据量超千万,且有明显分区维度(如时间序列)[^1][^3]。 ##### 方法3: **采用高效数据加载工具** 对于大批量数据(如从文件导入),原生SQL插入效率,应使用数据库专属批量加载工具。 - **原理**:工具如COPY命令或LOAD DATA INFILE绕过常规SQL解析,直接读写磁盘。 - **实施步骤**: 1. **使用COPY命令**:引用[2]推荐在Pandas中避免`to_sql()`,改用COPY方法(如PostgreSQL的`COPY FROM`或MySQL的`LOAD DATA INFILE`)。Python示例: ```python import psycopg2 conn = psycopg2.connect(database="test") cursor = conn.cursor() with open('data.csv', 'r') as f: cursor.copy_expert("COPY table_name FROM STDIN WITH CSV", f) conn.commit() ``` 2. **结合外部工具**:引用[4]提到备份工具可用数据库连接池和多线程加速上传,类似地,用Apache Spark或ETL工具并行处理数据加载[^4]。 - **优点**:速度极快(比常规插入快数十倍),适合亿级数据迁移。 - **缺点**:需文件系统权限,工具依赖性高。 - **最佳场景**:初始数据导入或定期批量更新[^2][^4]。 ##### 方法4: **分或分库** 如果数据量持续增长(如十亿级),分区可能不足,需分或分库分散负载。 - **原理**:将数据拆分到多或多数据库实例(如按用户ID分片)。 - **实施步骤**: 1. **设计分片策略**:如引用[1]所述基于手机尾号分,但更推荐按时间或业务键。 2. **修改代码**:在应用层添加路由逻辑(如查询前确定目标)。 3. **逐步迁移**:创建新分,逐步迁移旧数据。 - **优点**:高扩展性,支持海量数据。 - **缺点**:代码重构复杂,事务管理困难。 - **最佳场景**:超大规模系统,长期数据增长需求[^1]。 #### 3. **总结与建议** - **首选方案**:从批量插入入手(方法1),若无效则迁移到分区(方法2)。分区是高效成本的解决方案(引用[1]强调其“美滋滋”的优势)[^1]。 - **综合优化**:结合方法,例如分区+批量插入+COPY工具。 - **预防措施**:上传前压缩数据、禁用非关键索引、增加服务器资源。 - **注意事项**:测试环境验证方案,备份数据(引用[4]提及备份工具的重要性)[^4]。 如果提供更多细节(如数据库类型、数据量级),我可给出针对性建议。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值