索引下推:ICP优化

一、什么是索引下推(ICP) 1.1 问题场景 CREATE INDEX idx_a_b ON table(a, b); SELECT * FROM table WHERE a = 1 AND b LIKE '%abc%'; 传统执行(无ICP): 使用索引找到a=1的所有记录 回表获取完整数据 在Server层过滤b LIKE ‘%abc%’ 问题:大量无效回表。 1.2 ICP优化 ICP执行(MySQL 5.6+): 使用索引找到a=1的记录 在索引中直接过滤b LIKE ‘%abc%’ 只回表符合条件的记录 优势:减少回表次数。 二、ICP原理 2.1 传统流程 vs ICP流程 传统: 索引层:找到a=1的10条记录 ↓ 回表10次 存储引擎:获取10条完整数据 ↓ Server层:过滤b,最终2条符合 ICP: 索引层:找到a=1的10条记录 ↓ 在索引中过滤b 索引层:10条中2条符合b条件 ↓ 只回表2次 存储引擎:获取2条完整数据 2.2 关键 下推:将Server层的过滤条件下推到存储引擎层。 三、ICP的适用条件 3.1 启用条件 -- 查看ICP状态 SHOW VARIABLES LIKE 'optimizer_switch'; -- index_condition_pushdown=on -- 启用ICP SET optimizer_switch='index_condition_pushdown=on'; 3.2 使用场景 -- ✅ 可以使用ICP CREATE INDEX idx_a_b_c ON table(a, b, c); WHERE a = 1 AND b > 10 AND c = 3; -- a精确匹配 → 使用索引 -- b范围查询 → 使用索引 -- c过滤条件 → ICP下推到索引层 3.3 不适用场景 -- ❌ 主键索引(聚簇索引) -- 已经包含完整数据,无需下推 -- ❌ 覆盖索引 -- 不需要回表,无需下推 -- ❌ 全表扫描 -- 没有使用索引 四、EXPLAIN中的ICP 4.1 识别标志 EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status LIKE '%pending%'; 结果: ...

2025-11-20 · maneng

覆盖索引:减少回表查询

一、什么是覆盖索引 1.1 回表查询 -- 表结构 CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50), age INT, email VARCHAR(100) ); CREATE INDEX idx_name ON users(name); -- 查询 SELECT * FROM users WHERE name = '张三'; 查询过程: 在idx_name索引找到name=‘张三’,获取id=10 回表:到主键索引查找id=10的完整数据 返回结果 IO次数:2次(索引1次 + 回表1次) 1.2 覆盖索引 -- 查询 SELECT id, name FROM users WHERE name = '张三'; 查询过程: 在idx_name索引找到name=‘张三’ 索引中已包含id和name 直接返回,无需回表 IO次数:1次(只查索引) 覆盖索引:查询的列都在索引中,无需回表。 二、覆盖索引的优势 2.1 减少IO -- 无覆盖索引 SELECT * FROM users WHERE name = '张三'; -- IO:索引 + 回表 = 2次 -- 覆盖索引 SELECT id, name FROM users WHERE name = '张三'; -- IO:索引 = 1次 2.2 提升性能 -- 测试 -- 100万数据,查询10000次 -- SELECT *(需要回表) -- 耗时:5秒 -- SELECT id, name(覆盖索引) -- 耗时:0.5秒 -- 性能提升:10倍 三、如何实现覆盖索引 3.1 主键索引天然覆盖 -- 主键索引是聚簇索引,包含完整数据 SELECT * FROM users WHERE id = 10; -- 覆盖索引 3.2 设计联合索引 -- 查询 SELECT id, name, age FROM users WHERE name = '张三'; -- 索引设计 CREATE INDEX idx_name_age ON users(name, age); -- 索引包含:name, age, id(主键自动包含) -- 覆盖查询所需列 3.3 添加冗余列 -- 查询 SELECT user_id, order_no, status FROM orders WHERE user_id = 123; -- 索引设计 CREATE INDEX idx_user_no_status ON orders(user_id, order_no, status); -- order_no和status可能不在WHERE中,但在SELECT中 -- 添加到索引实现覆盖 四、EXPLAIN中的覆盖索引 4.1 Using index EXPLAIN SELECT id, name FROM users WHERE name = '张三'; 结果: ...

2025-11-20 · maneng

索引失效的场景分析

引言 创建了索引不一定会使用。理解索引失效的场景,才能写出高效的SQL。 一、违反最左前缀原则 1.1 联合索引规则 -- 索引 CREATE INDEX idx_a_b_c ON table(a, b, c); -- ✅ 使用索引 WHERE a = 1; WHERE a = 1 AND b = 2; WHERE a = 1 AND b = 2 AND c = 3; -- ❌ 不使用索引 WHERE b = 2; -- 缺少a WHERE c = 3; -- 缺少a和b WHERE b = 2 AND c = 3; -- 缺少a 1.2 跳跃列 -- 索引 CREATE INDEX idx_a_b_c ON table(a, b, c); -- ⚠️ 部分使用索引 WHERE a = 1 AND c = 3; -- 只使用a,c失效 二、索引列上使用函数 2.1 函数导致失效 -- ❌ 失效:YEAR函数 SELECT * FROM orders WHERE YEAR(created_at) = 2024; -- ✅ 改进:使用范围查询 SELECT * FROM orders WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31 23:59:59'; 2.2 常见函数失效案例 -- ❌ 失效 WHERE DATE(created_at) = '2024-11-21'; WHERE LEFT(name, 3) = 'abc'; WHERE UPPER(email) = 'USER@EXAMPLE.COM'; WHERE price * 0.8 > 100; -- ✅ 改进 WHERE created_at >= '2024-11-21' AND created_at < '2024-11-22'; WHERE name LIKE 'abc%'; WHERE email = 'user@example.com'; -- 应用层转小写 WHERE price > 125; -- 100 / 0.8 三、隐式类型转换 3.1 字符串与数字 -- 表结构 CREATE TABLE users ( id INT, phone VARCHAR(20) -- 字符串类型 ); CREATE INDEX idx_phone ON users(phone); -- ❌ 失效:数字查询字符串列 SELECT * FROM users WHERE phone = 13800138000; -- 数字 -- MySQL转换为:WHERE CAST(phone AS SIGNED) = 13800138000 -- 索引失效 -- ✅ 使用索引 SELECT * FROM users WHERE phone = '13800138000'; -- 字符串 3.2 反向情况 -- 表结构 CREATE TABLE users ( id INT, age INT -- 数字类型 ); CREATE INDEX idx_age ON users(age); -- ✅ 使用索引:字符串查询数字列 SELECT * FROM users WHERE age = '20'; -- MySQL转换为:WHERE age = CAST('20' AS SIGNED) -- 索引仍然有效 规则:字符串列查询时必须用字符串类型。 ...

2025-11-20 · maneng

索引的本质:为什么索引能加速查询?

引言 索引是数据库性能优化的关键。理解索引的本质,才能正确使用索引。 一、没有索引的查询 1.1 全表扫描 -- 查询id=1000的用户(无索引) SELECT * FROM users WHERE id = 1000; 执行过程: 从第1行开始 逐行检查id是否等于1000 找到后返回 如果表有100万行,最坏情况需要扫描100万行 时间复杂度:O(n) 1.2 问题 查询慢(全表扫描) 资源消耗大(CPU、磁盘IO) 并发性能差 二、索引的本质 2.1 从二分查找说起 # 在有序数组中查找 def binary_search(arr, target): left, right = 0, len(arr) - 1 while left <= right: mid = (left + right) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return -1 时间复杂度:O(log n) 关键:数据有序 + 快速定位 2.2 索引的原理 索引是一种排序的数据结构,存储列值和行位置的映射。 索引结构(简化示例): +-------+-----------+ | id值 | 行位置 | +-------+-----------+ | 1 | 0x1000 | | 10 | 0x1050 | | 100 | 0x1100 | | 1000 | 0x2000 | <-- 通过索引快速定位 +-------+-----------+ 查询过程: ...

2025-11-20 · maneng

性能调优:降低Sentinel开销

性能开销分析 Sentinel引入的开销 组件 开销 说明 Entry创建 < 0.1ms 创建Entry对象 Slot Chain < 0.2ms 责任链处理 统计更新 < 0.1ms 滑动窗口更新 总开销 < 0.5ms 正常情况下 压测对比 无Sentinel: - QPS: 10,000 - 平均RT: 10ms 有Sentinel: - QPS: 9,800 (-2%) - 平均RT: 10.4ms (+0.4ms) 结论:对性能影响< 5% 优化策略 1. 减少资源数量 // ❌ 不推荐:每个URL都是资源 @SentinelResource(value = "/order/{id}") public Order getOrder(@PathVariable Long id) { } // ✅ 推荐:按模块定义资源 @SentinelResource(value = "orderQuery") public Order getOrder(@PathVariable Long id) { } 2. 合并规则 // ❌ 不推荐:100个资源,100条规则 for (String resource : resources) { FlowRule rule = new FlowRule(); rule.setResource(resource); rule.setCount(1000); rules.add(rule); } // ✅ 推荐:按分组定义规则 FlowRule readRule = new FlowRule(); readRule.setResource("read_api"); readRule.setCount(10000); FlowRule writeRule = new FlowRule(); writeRule.setResource("write_api"); writeRule.setCount(1000); 3. 禁用不必要的Slot public class CustomSlotChainBuilder implements SlotChainBuilder { @Override public ProcessorSlotChain build() { ProcessorSlotChain chain = new DefaultProcessorSlotChain(); chain.addLast(new NodeSelectorSlot()); chain.addLast(new ClusterBuilderSlot()); chain.addLast(new StatisticSlot()); // 不需要授权,不添加AuthoritySlot // chain.addLast(new AuthoritySlot()); chain.addLast(new FlowSlot()); chain.addLast(new DegradeSlot()); return chain; } } 4. 调整滑动窗口参数 // 默认:2个窗口,1秒 // 适合QPS限流 // 高QPS场景:增加窗口数 SampleCountProperty.setSampleCount(4); // 4个窗口 IntervalProperty.setInterval(1000); // 1秒 // 低QPS场景:减少窗口数 SampleCountProperty.setSampleCount(1); // 1个窗口 5. 使用异步模式 // 同步模式 @SentinelResource("orderCreate") public Order createOrder(OrderDTO dto) { return orderService.create(dto); } // 异步模式 @SentinelResource("orderCreate") public CompletableFuture<Order> createOrderAsync(OrderDTO dto) { return CompletableFuture.supplyAsync(() -> orderService.create(dto)); } JVM参数优化 堆内存 # 合理设置堆内存,避免过度GC -Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m GC调优 # 使用G1 GC -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=16m # GC日志 -Xloggc:logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps 线程池 # Tomcat线程池 server.tomcat.threads.max=200 server.tomcat.threads.min-spare=50 server.tomcat.accept-count=100 批量操作优化 批量检查 // ❌ 不推荐:循环检查 for (Long productId : productIds) { Entry entry = SphU.entry("queryProduct"); try { queryProduct(productId); } finally { entry.exit(); } } // ✅ 推荐:批量检查一次 Entry entry = SphU.entry("queryProduct", EntryType.IN, productIds.size()); try { batchQueryProducts(productIds); } finally { entry.exit(); } 缓存优化 规则缓存 @Component public class RuleCacheManager { private final LoadingCache<String, List<FlowRule>> ruleCache; public RuleCacheManager() { this.ruleCache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.MINUTES) .build(new CacheLoader<String, List<FlowRule>>() { @Override public List<FlowRule> load(String resource) { return FlowRuleManager.getRules(resource); } }); } public List<FlowRule> getRules(String resource) { try { return ruleCache.get(resource); } catch (ExecutionException e) { return Collections.emptyList(); } } } 监控性能指标 性能指标 @Component public class SentinelPerformanceMonitor { private final Timer entryTimer; public SentinelPerformanceMonitor(MeterRegistry registry) { this.entryTimer = Timer.builder("sentinel.entry.timer") .description("Sentinel entry execution time") .register(registry); } @Around("@annotation(sentinelResource)") public Object monitor(ProceedingJoinPoint pjp, SentinelResource sentinelResource) throws Throwable { return entryTimer.record(() -> { try { return pjp.proceed(); } catch (Throwable e) { throw new RuntimeException(e); } }); } } 压测验证 压测脚本 #!/bin/bash # 压测接口 # 无Sentinel ab -n 100000 -c 100 http://localhost:8080/order/create # 有Sentinel # 修改代码添加@SentinelResource ab -n 100000 -c 100 http://localhost:8080/order/create # 对比结果 性能报告 压测结果对比: 指标 | 无Sentinel | 有Sentinel | 差异 -------------|-----------|-----------|------ QPS | 10,245 | 10,087 | -1.5% 平均RT(ms) | 9.76 | 9.91 | +1.5% 最大RT(ms) | 45 | 48 | +6.7% 错误率 | 0% | 0% | 0% 结论:性能影响在可接受范围内(< 2%) 最佳实践 1. 资源粒度 // ✅ 推荐:模块级资源 @SentinelResource("orderModule") // ❌ 避免:方法级资源(太多) @SentinelResource("orderService.createOrder") @SentinelResource("orderService.updateOrder") @SentinelResource("orderService.deleteOrder") 2. 规则数量 // ✅ 推荐:< 100条规则 // 每个应用的规则总数控制在100条以内 // ❌ 避免:> 1000条规则 // 规则过多会影响性能 3. 统计窗口 // 高QPS(> 1000):使用更多窗口 SampleCountProperty.setSampleCount(4); // 低QPS(< 100):使用更少窗口 SampleCountProperty.setSampleCount(1); 4. 集群模式 // 高流量场景使用集群模式 rule.setClusterMode(true); // 低流量场景使用本地模式 rule.setClusterMode(false); 性能测试 测试用例 @Test public void testSentinelPerformance() { // 预热 for (int i = 0; i < 10000; i++) { testEntry(); } // 测试 long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { testEntry(); } long cost = System.currentTimeMillis() - start; System.out.println("10万次Entry操作耗时:" + cost + "ms"); System.out.println("平均耗时:" + (cost / 100000.0) + "ms"); } private void testEntry() { try (Entry entry = SphU.entry("test")) { // 业务逻辑 } catch (BlockException e) { // 限流 } } 总结 性能优化要点: ...

2025-11-20 · maneng

排序与分页:ORDER BY与LIMIT

引言 在实际开发中,我们经常需要对查询结果进行排序和分页: 商品列表按价格从低到高排序 文章列表按发布时间倒序显示 用户列表分页展示,每页20条 排行榜按得分从高到低排序 这些需求都需要通过 ORDER BY 和 LIMIT 来实现。 为什么排序和分页如此重要? 用户体验:有序的数据更符合用户的阅读习惯 性能优化:分页可以减少数据传输量,提升响应速度 业务需求:排行榜、Top N查询等场景必不可少 数据管理:便于数据的浏览和检索 本文将深入讲解排序和分页的原理、语法、性能优化技巧,以及如何解决深分页问题。 一、ORDER BY 排序基础 1.1 基本语法 SELECT column1, column2, ... FROM table_name WHERE condition ORDER BY column1 [ASC|DESC], column2 [ASC|DESC], ...; 执行顺序: FROM:确定要查询的表 WHERE:过滤出符合条件的行 ORDER BY:对结果进行排序 SELECT:选择要返回的列 排序方向: ASC:升序(Ascending),从小到大,默认值 DESC:降序(Descending),从大到小 1.2 准备测试数据 继续使用上一篇的商品表,并添加一些新数据: -- 补充更多测试数据 INSERT INTO products (name, category, price, stock, created_at, description) VALUES ('iPhone 14', '手机', 5999.00, 100, '2024-09-01', 'Apple上代旗舰'), ('小米13', '手机', 3299.00, 150, '2024-03-15', '小米上代旗舰'), ('华为P60', '手机', 4999.00, 80, '2024-04-20', '华为影像旗舰'), ('戴尔XPS', '电脑', 9999.00, 25, '2024-05-10', '戴尔高端笔记本'), ('Surface Pro', '平板', 6999.00, 40, '2024-06-15', '微软二合一平板'); 二、单字段排序 2.1 数值字段排序 -- 按价格升序排列(从低到高) SELECT name, price FROM products ORDER BY price ASC; -- 等价写法(ASC可省略) SELECT name, price FROM products ORDER BY price; 结果: ...

2025-11-20 · maneng

Java并发01:为什么需要并发编程 - 从单线程到多线程的演进

引言:一个Web服务器的故事 假设你正在开发一个简单的Web服务器,用户访问网站时,服务器需要处理请求并返回响应。最初,你可能会写出这样的代码: public class SimpleWebServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("服务器启动,监听端口 8080..."); while (true) { // 接受客户端连接 Socket client = serverSocket.accept(); // 处理请求(耗时操作) handleRequest(client); // 关闭连接 client.close(); } } private static void handleRequest(Socket client) throws IOException { // 读取请求 BufferedReader in = new BufferedReader( new InputStreamReader(client.getInputStream())); String request = in.readLine(); // 模拟业务处理:查询数据库、调用外部API等(耗时500ms) try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // 返回响应 PrintWriter out = new PrintWriter(client.getOutputStream(), true); out.println("HTTP/1.1 200 OK"); out.println("Content-Type: text/plain"); out.println(); out.println("Hello, World!"); } } 问题来了: ...

2025-11-20 · maneng

StampedLock性能优化:比读写锁更快的乐观读锁

一、ReadWriteLock的性能瓶颈 1.1 读写锁的问题 // ReadWriteLock:读多写少场景的优化 ReadWriteLock rwLock = new ReentrantReadWriteLock(); // 读操作 rwLock.readLock().lock(); try { // 读取数据 return data; } finally { rwLock.readLock().unlock(); } // 写操作 rwLock.writeLock().lock(); try { // 修改数据 data = newData; } finally { rwLock.writeLock().unlock(); } 性能瓶颈: 读锁也是锁:虽然允许多个读线程并发,但获取/释放锁仍有开销 CAS竞争:多个读线程获取读锁时,仍需CAS操作state变量 写锁饥饿:大量读操作时,写操作可能长时间等待 1.2 StampedLock的突破 StampedLock sl = new StampedLock(); // 乐观读(无锁) long stamp = sl.tryOptimisticRead(); // 获取戳记(无锁) int value = data; // 读取数据 if (sl.validate(stamp)) { // 验证戳记是否有效 return value; // 有效,直接返回 } // 无效:升级为悲观读锁 stamp = sl.readLock(); try { return data; } finally { sl.unlockRead(stamp); } 核心优势: ...

2025-11-20 · maneng

无锁编程与LongAdder:高并发计数器的性能优化

一、AtomicLong的性能瓶颈 1.1 高并发下的问题 // 1000个线程,每个线程累加100万次 AtomicLong counter = new AtomicLong(0); ExecutorService executor = Executors.newFixedThreadPool(1000); for (int i = 0; i < 1000; i++) { executor.submit(() -> { for (int j = 0; j < 1_000_000; j++) { counter.incrementAndGet(); // CAS操作 } }); } // 耗时:约 15秒(高并发场景) 性能瓶颈: CAS自旋:高并发时,CAS失败率高,大量自旋消耗CPU 缓存失效:所有线程竞争同一个变量,导致CPU缓存频繁失效(Cache Line伪共享) 串行化:本质上是串行执行,无法充分利用多核CPU 1.2 LongAdder的解决方案 // 同样的场景,使用LongAdder LongAdder counter = new LongAdder(); ExecutorService executor = Executors.newFixedThreadPool(1000); for (int i = 0; i < 1000; i++) { executor.submit(() -> { for (int j = 0; j < 1_000_000; j++) { counter.increment(); // 分段累加 } }); } long result = counter.sum(); // 最终求和 // 耗时:约 2秒(高并发场景) // 性能提升:约 7.5倍! 为什么更快? ...

2025-11-19 · maneng

RocketMQ进阶06:批量消息优化 - 提升吞吐量的利器

引言:批量的力量 单条发送 vs 批量发送: 单条:1万TPS 批量:10万TPS(提升10倍) 批量发送 List<Message> messages = new ArrayList<>(); for (int i = 0; i < 100; i++) { Message msg = new Message("topic", ("Msg" + i).getBytes()); messages.add(msg); } // 批量发送(一次网络调用) SendResult result = producer.send(messages); 批量大小限制 单批最大:4MB 建议:每批100-1000条消息 性能对比 方式 TPS 延迟 单条发送 1万 5ms 批量发送 10万 10ms 本文关键词:批量消息 性能优化 高吞吐量

2025-11-14 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...