如何用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
相关产品推荐
相关产品推荐

