lib/shiba/explain/checks.rb in shiba-0.4.0 vs lib/shiba/explain/checks.rb in shiba-0.5.0

- old
+ new

@@ -4,16 +4,17 @@ class Explain class Checks include CheckSupport extend CheckSupport::ClassMethods - def initialize(rows, index, stats, options, result) + def initialize(rows, index, stats, options, query, result) @rows = rows @row = rows[index] @index = index @stats = stats @options = options + @query = query @result = result @tbl_message = {} end attr_reader :cost @@ -28,10 +29,30 @@ def add_message(tag, extra = {}) @result.messages << { tag: tag, table_size: table_size, table: table }.merge(extra) end + # TODO: need to parse SQL here I think + def simple_table_scan? + @rows.size == 1 && + (@row['using_index'] || !(@query.sql =~ /\s+WHERE\s+/i)) && + (@row['access_type'] == "index" || (@query.sql !~ /order by/i)) && + @query.limit + end + + # TODO: we don't catch some cases like SELECT * from foo where index_col = 1 limit 1 + # bcs we really just need to parse the SQL. + check :check_simple_table_scan + def check_simple_table_scan + if simple_table_scan? + rows_read = [@query.limit, table_size].min + @cost = @result.cost = rows_read * Shiba::Explain::COST_PER_ROW_READ + @result.messages << { tag: 'limited_scan', cost: @result.cost, table: table, rows_read: rows_read } + end + end + + check :check_derived def check_derived if table =~ /<derived.*?>/ # select count(*) from ( select 1 from foo where blah ) add_message('derived_table', size: nil) @@ -73,55 +94,49 @@ end end check :check_key_size def check_key_size - if @row['key'] - rows_read = @stats.estimate_key(table, @row['key'], @row['used_key_parts']) + if @access_type == "access_type_index" + # access-type index means a table-scan as performed on an index... all rows. + key_size = table_size + elsif @row['key'] + key_size = @stats.estimate_key(table, @row['key'], @row['used_key_parts']) else - rows_read = table_size + key_size = table_size end # TBD: this appears to come from a couple of bugs. # one is we're not handling mysql index-merges, the other is that # we're not handling mysql table aliasing. - if rows_read.nil? - rows_read = 1 + if key_size.nil? + key_size = 1 end if @row['join_ref'] - # when joining, we'll say we read "@cost" rows -- but up to + # when joining, we'll say we read "key_size * (previous result size)" rows -- but up to # a max of the table size. I'm not sure this assumption is *exactly* # true but it feels good enough to start; a decent hash join should # nullify the cost of re-reading rows. I think. - @cost = [@result.result_size * rows_read, table_size || 2**32].min + rows_read = [@result.result_size * key_size, table_size || 2**32].min # poke holes in this. Is this even remotely accurate? # We're saying that if we join to a a table with 100 rows per item # in the index, for each row we'll be joining in 100 more rows. Is that true? - @result.result_size *= rows_read + @result.result_size *= key_size else - @cost = rows_read - @result.result_size += rows_read + rows_read = key_size + @result.result_size += key_size end + @cost = Shiba::Explain::COST_PER_ROW_READ * rows_read @result.cost += @cost @tbl_message['cost'] = @cost + @tbl_message['rows_read'] = rows_read @tbl_message['index'] = @row['key'] @tbl_message['index_used'] = @row['used_key_parts'] add_message(@access_type, @tbl_message) - end - - def estimate_row_count_with_key(key) - explain = Explain.new(@sql, @stats, @backtrace, force_key: key) - explain.run_checks! - rescue Mysql2::Error => e - if /Key .+? doesn't exist in table/ =~ e.message - return nil - end - - raise e end def run_checks! _run_checks! do :stop if @cost