You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用RSpec检测MySQL缺失索引?适配Sinatra+Sequel Gem栈

识别MySQL缺失索引的可行方案(适配RSpec + Sinatra/Sequel/MySQL)

一、ORM无关的通用方案(靠MySQL自带工具搞定)

这个方案不绑定任何ORM,直接用MySQL的慢查询日志和EXPLAIN分析,适合所有基于MySQL的项目:

  • 第一步:测试前临时调整MySQL配置
    在spec_helper.rb里加个全局钩子,启动测试时打开慢查询日志,把阈值设为0(这样所有查询都会被记录),同时开启“记录未使用索引的查询”:
    RSpec.configure do |config|
      config.before(:suite) do
        # 临时开启慢查询相关配置
        DB.run("SET GLOBAL slow_query_log = 'ON'")
        DB.run("SET GLOBAL long_query_time = 0")
        DB.run("SET GLOBAL log_queries_not_using_indexes = 'ON'")
      end
    
      config.after(:suite) do
        # 测试完记得恢复默认配置,别影响其他环境
        DB.run("SET GLOBAL slow_query_log = 'OFF'")
        DB.run("SET GLOBAL long_query_time = 10") # 改回你平时用的阈值就行
        DB.run("SET GLOBAL log_queries_not_using_indexes = 'OFF'")
      end
    end
    
  • 第二步:解析慢查询日志找问题
    测试跑完后,去MySQL的慢查询日志文件里捞数据(路径可以用SHOW VARIABLES LIKE 'slow_query_log_file'查),重点找带Using filesort、Using temporary或者type: ALL的查询——这些基本都是没用到索引或者索引效率极低的情况。
  • 第三步:自动跑EXPLAIN确认(可选)
    不想手动看日志的话,写个脚本自动解析日志,对可疑查询跑EXPLAIN。比如在after钩子加这段:
    config.after(:suite) do
      slow_log_path = DB.fetch("SHOW VARIABLES LIKE 'slow_query_log_file'").first[:Value]
      # 提取日志里的查询语句
      queries = File.read(slow_log_path).split("\n").grep(/Query_time/).map { |line| line.split(/\# Query_time: .*? /).last }.uniq
      queries.each do |query|
        next if query.strip.empty?
        explain_result = DB.fetch("EXPLAIN #{query}").first
        if explain_result[:type] == 'ALL' || explain_result[:Extra].include?('Using filesort') || explain_result[:Extra].include?('Using temporary')
          puts "⚠️  可能缺失索引的查询:\n#{query}\nEXPLAIN详情:#{explain_result.inspect}\n"
        end
      end
    end
    

二、适配Sequel ORM的方案

既然用了Sequel,直接在ORM层做手脚更方便:

  • 方案1:Sequel查询钩子+实时EXPLAIN分析
    给Sequel加个日志钩子,每次执行SELECT查询时自动跑EXPLAIN,一旦发现没用到索引就输出警告:
    DB = Sequel.connect(ENV['DATABASE_URL'])
    
    # 给Sequel加查询分析钩子
    DB.loggers << lambda do |sql, _, args|
      next unless sql.start_with?('SELECT') # 只分析查询语句
      # 处理带绑定参数的查询,把参数替换成实际值才能跑EXPLAIN
      if args.any?
        # 用Sequel的literal方法把参数转成SQL合法格式
        sql_with_args = sql.gsub(/\?/) { DB.literal(args.shift) }
      else
        sql_with_args = sql
      end
      begin
        explain = DB.fetch("EXPLAIN #{sql_with_args}").first
        if explain[:type] == 'ALL' || explain[:Extra].include?('Using filesort') || explain[:Extra].include?('Using temporary')
          # 在RSpec的测试报告里输出警告
          RSpec.configuration.reporter.message("⚠️  潜在索引缺失:\n#{sql_with_args}\n索引使用情况:#{explain[:key] || '无'}\nExtra信息:#{explain[:Extra]}")
        end
      rescue => e
        # 跳过解析失败的查询(比如复杂子查询)
        puts "解析查询失败:#{e.message}"
      end
    end
    
  • 方案2:本地开发用Sequel Pro辅助
    本地跑测试的时候,用Sequel Pro连数据库,开启“查询分析”面板,实时看测试执行的查询有没有用索引。这个是手动操作,适合开发阶段快速排查,自动化测试还是用上面的钩子方案。

三、自定义RSpec断言(针对性测试)

如果想在特定测试用例里强制检查索引使用,可以写个自定义断言:

RSpec::Matchers.define :use_index do |expected_index|
  match do |query|
    explain = DB.fetch("EXPLAIN #{query}").first
    explain[:key] == expected_index
  end

  failure_message do |query|
    explain = DB.fetch("EXPLAIN #{query}").first
    "期望查询使用索引 #{expected_index},但实际用了 #{explain[:key] || '无'}。EXPLAIN详情:#{explain.inspect}"
  end
end

# 测试里这么用
it "通过邮箱查询用户时使用索引" do
  user = User.create(email: 'test@example.com')
  query = "SELECT * FROM users WHERE email = '#{user.email}'"
  expect(query).to use_index('index_users_on_email')
end

内容的提问来源于stack exchange,提问作者gayavat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 02:57:48