【分享】千万级的大表性能调优解决方案
mhr18 2025-05-08 03:00 14 浏览 0 评论
前言
大表优化是一个老生常谈的话题,但随着业务规模的增长,总有人会“中招”。很多小伙伴的数据库在刚开始的时候表现良好,查询也很流畅,但一旦表中的数据量上了千万级,性能问题就开始浮现,查询慢、写入卡、分页拖沓、甚至偶尔直接宕机。这时大家可能会想,是不是数据库不行?是不是需要升级到更强的硬件?
其实很多情况下,根本问题在于没做好优化。今天,我们就从问题本质讲起,逐步分析大表常见的性能瓶颈,以及如何一步步优化。
一、为什么大表会慢?
在搞优化之前,先搞清楚大表性能问题的根本原因。数据量大了,为什么数据库就慢了?
1.磁盘IO瓶颈
大表的数据是存储在磁盘上的,数据库的查询通常会涉及到数据块的读取。当数据量很大时,单次查询可能需要从多个磁盘块中读取大量数据,磁盘的读写速度会直接限制查询性能。
举例:
假设有一张订单表orders,里面存了5000万条数据,你想要查询某个用户的最近10条订单:
SELECT * FROM orders WHERE user_id = 123 ORDER BY order_time DESC LIMIT 10;
如果没有索引,数据库会扫描整个表的所有数据,再进行排序,性能肯定会拉胯。
2.索引失效或没有索引
如果表的查询没有命中索引,数据库会进行全表扫描(Full Table Scan),也就是把表里的所有数据逐行读一遍。这种操作在千万级别的数据下非常消耗资源,性能会急剧下降。
举例:
比如你在查询时写了这样的条件:
SELECT * FROM orders WHERE DATE(order_time) = '2023-01-01';
这里用了DATE()函数,数据库需要对所有记录的order_time字段进行计算,导致索引失效。
3.分页性能下降
分页查询是大表中很常见的场景,但深度分页(比如第100页之后)会导致性能问题。即使你只需要10条数据,但数据库仍然需要先扫描出前面所有的记录。
举例:
查询第1000页的10条数据:
SELECT * FROM orders ORDER BY order_time DESC LIMIT 9990, 10;
这条SQL实际上是让数据库先取出前9990条数据,然后丢掉,再返回后面的10条。随着页码的增加,查询的性能会越来越差。
4.锁争用
在高并发场景下,多个线程同时对同一张表进行增删改查操作,会导致行锁或表锁的争用,进而影响性能。
二、性能优化的总体思路
性能优化的本质是减少不必要的IO、计算和锁竞争,目标是让数据库尽量少做“无用功”。优化的总体思路可以总结为以下几点:
- 表结构设计要合理:尽量避免不必要的字段,数据能拆分则拆分。
- 索引要高效:设计合理的索引结构,避免索引失效。
- SQL要优化:查询条件精准,尽量减少全表扫描。
- 分库分表:通过水平拆分、垂直拆分减少单表数据量。
- 缓存和异步化:减少对数据库的直接压力。
接下来,我们逐一展开。
三、表结构设计优化
表结构是数据库性能优化的基础,设计不合理的表结构会导致后续的查询和存储性能问题。
1. 精简字段类型
字段的类型决定了存储的大小和查询的性能。
- 能用INT的不要用BIGINT。
- 能用VARCHAR(100)的不要用TEXT。
- 时间字段建议用TIMESTAMP或DATETIME,不要用CHAR或VARCHAR来存时间。
举例:
-- 不推荐
CREATE TABLE orders (
id BIGINT,
user_id BIGINT,
order_status VARCHAR(255),
remarks TEXT
);
-- 优化后
CREATE TABLE orders (
id BIGINT,
user_id INT UNSIGNED,
order_status TINYINT, -- 状态用枚举表示
remarks VARCHAR(500) -- 限制最大长度
);
这样可以节省存储空间,查询时也更高效。
2. 表拆分:垂直拆分与水平拆分
垂直拆分
当表中字段过多,某些字段并不是经常查询的,可以将表按照业务逻辑拆分为多个小表。
示例:
将订单表分为两个表:orders_basic 和 orders_details。
-- 基本信息表
CREATE TABLE orders_basic (
id BIGINT PRIMARY KEY,
user_id INT UNSIGNED,
order_time TIMESTAMP
);
-- 详情表
CREATE TABLE orders_details (
id BIGINT PRIMARY KEY,
remarks VARCHAR(500),
shipping_address VARCHAR(255)
);
水平拆分
当单表的数据量过大时,可以按一定规则拆分到多张表中。
示例:
假设我们按用户ID对订单表进行水平拆分:
orders_0 -- 存user_id % 2 = 0的订单
orders_1 -- 存user_id % 2 = 1的订单
拆分后每张表的数据量大幅减少,查询性能会显著提升。
四、索引优化
索引是数据库性能优化的“第一杀器”,但很多人对索引的使用并不熟悉,导致性能不升反降。
1. 创建合适的索引
为高频查询的字段创建索引,比如主键、外键、查询条件字段。
示例:
CREATE INDEX idx_user_id_order_time ON orders (user_id, order_time DESC);
上面的复合索引可以同时加速user_id和order_time的查询。
2. 避免索引失效
- 别对索引字段使用函数或运算。
错误: - SELECT * FROM orders WHERE DATE(order_time) = '2023-01-01';
- 优化:
- SELECT * FROM orders WHERE order_time >= '2023-01-01 00:00:00' AND order_time < '2023-01-02 00:00:00';
- 注意隐式类型转换。
错误: - SELECT * FROM orders WHERE user_id = '123';
- 优化:
- SELECT * FROM orders WHERE user_id = 123;
五、SQL优化
1. 减少查询字段
只查询需要的字段,避免SELECT *。
-- 错误
SELECT * FROM orders WHERE user_id = 123;
-- 优化
SELECT id, order_time FROM orders WHERE user_id = 123;
2. 分页优化
深度分页时,使用“延迟游标”的方式避免扫描过多数据。
-- 深分页(性能较差)
SELECT * FROM orders ORDER BY order_time DESC LIMIT 9990, 10;
-- 优化:使用游标
SELECT * FROM orders WHERE order_time < '2023-01-01 12:00:00'
ORDER BY order_time DESC LIMIT 10;
六、分库分表
1. 水平分库分表
当单表拆分后仍无法满足性能需求,可以通过分库分表将数据分散到多个数据库中。
常见的分库分表规则:
- 按用户ID取模。
- 按时间分区。
七、缓存与异步化
1. 使用Redis缓存热点数据
对高频查询的数据可以存储到Redis中,减少对数据库的直接访问。
示例:
// 从缓存读取数据
String result = redis.get("orders:user:123");
if (result == null) {
result = database.query("SELECT * FROM orders WHERE user_id = 123");
redis.set("orders:user:123", result, 3600); // 设置缓存1小时
}
2. 使用消息队列异步处理写操作
高并发写入时,可以将写操作放入消息队列(如Kafka),然后异步批量写入数据库,减轻数据库压力。
八、实战案例
问题:
某电商系统的订单表存储了5000万条记录,用户查询订单详情时,页面加载时间超过10秒。
解决方案:
- 垂直拆分订单表:将订单详情字段拆分到另一个表中。
- 创建复合索引:为user_id和order_time创建索引。
- 使用Redis缓存:将最近30天的订单缓存到Redis中。
- 分页优化:使用search_after代替LIMIT深分页。
九、总结
大表性能优化是一个系统性工程,需要从表结构、索引、SQL到架构设计全方位考虑。千万级别的数据量看似庞大,但通过合理的拆分、索引设计和缓存策略,可以让数据库轻松应对。最重要的是,根据业务特点选择合适的优化策略,切勿盲目追求“高大上”的方案。希望这些经验能帮到你!
本文转载自【苏三说技术】。
如果你对技术感兴趣,请点赞+转发+关注,大家的支持是我分享最大的动力!!!
相关推荐
- MYSQL数据同步(mysql数据同步方式)
-
java开发工程师在实际的开发经常会需要实现两台不同机器上的MySQL数据库的数据同步,要解决这个问题不难,无非就是mysql数据库的数据同步问题。但要看你是一次性的数据同步需求,还是定时数据同步,亦...
- SpringBoot+Redis实现点赞收藏功能+定时同步数据库
-
由于点赞收藏都是高频率的操作,如果因此频繁地写入数据库会造成数据库压力比较大,因此采用redis来统计点赞收藏浏览量,之后定时一次性写入数据库中,缓解数据库地压力。一.大体思路设计redis中的储存结...
- 双11订单洪峰:Codis代理层如何扛住Redis集群搞不定的120万QPS?
-
双11订单洪峰下的技术挑战每年的双11购物节,都是对电商平台技术架构的极限考验。当零点钟声敲响,海量用户瞬间涌入,订单量呈指数级增长,系统需要承受每秒数十万甚至上百万次的请求。作为电商系统的核心组件之...
- 基于spring boot + MybatisPlus 商城管理系统的Java开源商城系统
-
前言Mall4j项目致力于为中小企业打造一个完整、易于维护的开源的电商系统,采用现阶段流行技术实现。后台管理系统包含商品管理、订单管理、运费模板、规格管理、会员管理、运营管理、内容管理、统计报表、权限...
- 商品券后价产品设计方案(显示券后价)
-
如何设计一套高效、准确且稳定的券后价计算系统,是电商产品设计中的关键挑战之一。本文详细介绍了商品券后价的产品设计方案,从背景目标、功能设计、系统实现逻辑到异常处理机制等多个方面进行了全面阐述。一、背景...
- 外观(门面)模式-Java实现(java 门面模式)
-
定义外观模式(FacadePattern),也叫门面模式,原始定义是:为了子系统中的一组接口提供统一的接口。定义一个更高级别的接口,使子系统更易于使用。大大降低应用程序的复杂度,提高了程序的可维护性...
- Mall - 用 SpringBoot 实现一个电商系统
-
目前最为主流的Web开发技术,包括SpringBoot、MyBatis、MongoDB、Kibina、Docker、Vue等,都是开发者十分需要掌握的技术。有没有一个全面而又实际的项目,能把这...
- 腾讯云国际站:哪些工具能实现可视化运维?
-
本文由【云老大】TG@yunlaoda360撰写开源工具Grafana:开源的可视化平台,可与Prometheus、Elasticsearch、MySQL等多种数据源集成,将复杂监控数据转化...
- 系统稳定性保障全流程实战:事前、事中、事后 Java 代码详解
-
在互联网架构中,系统稳定性是生命线。本文基于“事前预防、事中管控、事后复盘”三阶段模型,结合Java实战代码,深度解析如何构建高可用系统,让你的服务稳如磐石!一、事前:未雨绸缪,筑牢防线1.发...
- Java面试题:拆分微服务应该注意哪些地方方,如何拆分?
-
在拆分微服务时,需要综合考虑业务、技术和组织等多方面因素,以下是关键注意事项及拆分策略的详细说明:一、拆分注意事项1.业务边界清晰化单一职责原则:每个服务应专注于单一业务能力,例如订单服务仅处理订单...
- 软件性能调优全攻略:从瓶颈定位到工具应用
-
性能调优是软件测试中的重要环节,旨在提高系统的响应时间、吞吐量、并发能力、资源利用率,并降低系统崩溃或卡顿的风险。通常,性能调优涉及发现性能瓶颈、分析问题根因、优化代码和系统配置等步骤,调优之前需要先...
- Docker Compose实战,多容器协同编排的利器,让开发部署更高效!
-
开篇导读你是否有过这样的经历?启动一个项目,数据库、Redis、Web服务得一个个敲dockerrun?想让别人复现你的开发环境,却得发一堆复杂的启动命令?明明都是容器,为什么不能“一键启动”所...
- 如何设计Agent的记忆系统(agent记忆方法)
-
最近看了一张画Agent记忆分类的图我觉得分类分的还可以,但是太浅了,于是就着它的逻辑,仔细得写了一下在不同的记忆层,该如何设计和选型先从流程,作用,实力和持续时间的这4个维度来解释一下这几种记忆:1...
- 不了解业务和技术术语怎么做好产品和项目?
-
基础技术术语术语分类解释API开发技术应用程序接口,不同系统间数据交互的协议(如支付接口、地图接口)。SDK开发工具软件开发工具包,包含API、文档和示例代码,帮助快速接入服务。RESTfulAPI...
- Docker 架构详解与核心概念实战图解:一文读懂容器的前世今生
-
不懂Docker架构,你只是“用容器的人”;理解了它的底层逻辑,才能成为真正的高手!在学习Docker之前,很多同学可能会陷入一个误区:“反正我用dockerrun就能跑起服务,架构这种...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)