突破性能瓶颈!3大维度吃透SQL优化:索引设计→慢查询→ShardingSphere实战

一、索引设计:数据库的「高速公路」

1. 索引基础原理

  • B+Tree索引
    • 结构特点:叶子节点形成链表,适合范围查询(如 WHERE age > 20
    • 文件排序:非叶子节点只存索引值,叶子节点存完整数据或主键(决定是否回表)
  • 哈希索引
    • 适用场景:精确匹配(如 WHERE user_id = 1001
    • 限制:不支持范围查询,内存消耗大(常用于内存表)

2. 索引设计策略

场景 优化方案 示例
高频查询 联合索引覆盖查询字段 INDEX (user_id, status) 覆盖 SELECT status FROM orders WHERE user_id=123
排序优化 索引顺序匹配排序字段 INDEX (create_time DESC) 优化 ORDER BY create_time DESC
模糊查询 倒序存储+前缀索引 手机号 138****1234 存为 4321****831,用 LIKE '4321%' 避免左模糊

3. 索引失效的典型场景

  • 隐式类型转换

    sql 复制代码
    -- user_id 是字符串类型,但用数字查询(导致全表扫描)
    SELECT * FROM users WHERE user_id = 10086; 
  • 函数操作索引列

    sql 复制代码
    -- 索引失效:对 create_time 使用函数
    SELECT * FROM logs WHERE DATE(create_time) = '2023-10-01';

二、慢查询分析:数据库的「体检报告」

1. 诊断工具链

  • 慢查询日志配置

    sql 复制代码
    -- 开启慢查询日志(阈值设为1秒)
    SET GLOBAL slow_query_log = ON;
    SET GLOBAL long_query_time = 1;
  • 执行计划解读

    sql 复制代码
    EXPLAIN SELECT * FROM orders WHERE amount > 100;
    • 关键字段解析:
      • typeconst(主键) > ref(普通索引) > ALL(全表扫描)
      • rows:预估扫描行数(越小越好)
      • ExtraUsing filesort(需优化排序字段索引)

2. 高频性能问题及优化

  • 深度分页优化

    sql 复制代码
    -- 低效写法(扫描前100000行)
    SELECT * FROM orders LIMIT 100000, 10;
    
    -- 优化写法(利用覆盖索引)
    SELECT * FROM orders 
    WHERE id > (SELECT id FROM orders ORDER BY id LIMIT 100000, 1)
    LIMIT 10;
  • 大表COUNT优化

    • 方案:使用 Redis 计数器 或 MySQL 汇总表(定期更新总数)

三、分库分表:数据库的「分布式扩展」

1. 何时需要分库分表?

指标 阈值参考 处理方案
单表行数 > 500万 分区表 → 读写分离 → 分片
数据体积 > 50GB 归档历史数据 → 分库分表
QPS峰值 > 5000 缓存优化 → 分库分表

2. ShardingSphere核心功能

分片策略

  • 分片键选择

    • 要求:离散度高(如用户ID)、业务相关性(如订单时间)
  • 分片算法

    yaml 复制代码
    # 按用户ID取模分库(2个库)
    shardingAlgorithms:
      user_id_mod: 
        type: MOD
        props:
          sharding-count: 2

分布式事务

  • XA模式:强一致性,性能较低(适合资金交易)
  • Seata AT模式:最终一致性,高性能(适合订单状态更新)

3. 分库分表后的问题与解法

  • 跨分片查询
    • 方案:
      1. 业务层合并结果(如查询用户最近3月订单)
      2. 建立全局索引表(牺牲写性能换查询效率)

何为全局索引表

以下以 MySQL 为例,展示创建全局索引表的 SQL 语句:

sql 复制代码
-- 创建全局索引表
CREATE TABLE global_index_table (
    -- 全局唯一标识,通常使用 UUID 或业务主键
    global_id VARCHAR(36) NOT NULL PRIMARY KEY,
    -- 用于查询的索引字段,例如用户 ID
    index_field VARCHAR(20) NOT NULL,
    -- 分库标识
    db_shard_id INT NOT NULL,
    -- 分表标识
    table_shard_id INT NOT NULL,
    -- 其他必要的信息
    other_info VARCHAR(255),
    -- 创建索引,提高查询性能
    INDEX idx_index_field (index_field)
);

同步数据到全局索引表

在数据插入、更新或删除时,需要同步更新全局索引表。可以通过以下几种方式实现:

  • 应用层代码实现:在应用程序中编写代码,在数据操作时同时更新全局索引表。例如,在插入订单数据时,同时插入一条对应的全局索引记录。
java 复制代码
// Java 示例代码
public void insertOrder(Order order) {
    // 插入订单数据到分库分表
    orderDao.insert(order);

    // 插入全局索引记录
    GlobalIndex globalIndex = new GlobalIndex();
    globalIndex.setGlobalId(UUID.randomUUID().toString());
    globalIndex.setIndexField(order.getUserId());
    globalIndex.setDbShardId(order.getDbShardId());
    globalIndex.setTableShardId(order.getTableShardId());
    globalIndexDao.insert(globalIndex);
}
  • 数据库触发器实现:在数据库中创建触发器,当数据发生变化时自动更新全局索引表。例如,在订单表上创建插入触发器:
sql 复制代码
-- 创建插入触发器
DELIMITER //
CREATE TRIGGER insert_order_trigger
AFTER INSERT ON order_table
FOR EACH ROW
BEGIN
    INSERT INTO global_index_table (global_id, index_field, db_shard_id, table_shard_id)
    VALUES (UUID(), NEW.user_id, NEW.db_shard_id, NEW.table_shard_id);
END //
DELIMITER ;
  • 数据校验和修复:定期对全局索引表进行数据校验,发现不一致的数据及时进行修复。

使用全局索引表进行查询

在进行查询时,首先通过全局索引表定位到数据所在的分库分表,然后再到相应的分库分表中查询具体的数据。

sql 复制代码
-- 根据用户 ID 查询订单信息
SELECT *
FROM order_table
WHERE db_shard_id = (SELECT db_shard_id FROM global_index_table WHERE index_field = 'user_id')
  AND table_shard_id = (SELECT table_shard_id FROM global_index_table WHERE index_field = 'user_id');
  • 数据倾斜
    • 解法:动态分片(如按热点用户ID单独分库)

演进路径

  1. 单库阶段
    • 索引优化:(user_id, create_time) 联合索引
    • 查询优化:避免 SELECT *,使用覆盖索引
  2. 读写分离
    • 主库处理写操作,3个从库负载均衡读请求
  3. 分库分表
    • 垂直分库:拆分为 订单库用户库
    • 水平分表:订单表按 user_id % 8 分8个库,每个库按 月份 分12张表

工具推荐

  • 压测工具sysbench(模拟高并发订单创建)
  • 监控平台Prometheus + Grafana(跟踪慢查询与分片负载)
相关推荐
知识浅谈12 分钟前
@Validate 注解的使用-分组案例很有用
java·springboot
uhakadotcom23 分钟前
Pandas DataFrame 入门教程
后端·面试·github
Trouvaille ~24 分钟前
【Java篇】一法不变,万象归一:方法封装与递归的思想之道
java·开发语言·面向对象·javase·递归·方法·基础入门
划水哥~27 分钟前
SQL99 多表查询
数据库·sql
Zhava27 分钟前
MybatisPlus中的customSqlSegment动态拼接where条件
java·mybatis
Asthenia041229 分钟前
深入理解 Java 中的 ThreadLocal:从传统局限到 TransmittableThreadLocal 的解决方案
后端
王ASC31 分钟前
kettle的转换中sql不按设计顺序执行原因分析与解决办法
数据库·sql
极客先躯33 分钟前
高级java每日一道面试题-2025年2月26日-框架篇[Mybatis篇]-Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式 ?
java·mybatis·嵌套映射·resulttype属性·resultmap属性·results注解·列名别名
勇哥java实战分享37 分钟前
一次非常典型的 JVM OOM 事故 (要注意 where 1 = 1 哦)
后端
Elastic 中国社区官方博客1 小时前
Elasticsearch:语义文本 - 更简单、更好、更精炼、更强大 8.18
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索