Class: Shiba::Explain::Checks

Inherits:
Object
  • Object
show all
Extended by:
Shiba::Explain::CheckSupport::ClassMethods
Includes:
CheckSupport
Defined in:
lib/shiba/explain/checks.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Shiba::Explain::CheckSupport::ClassMethods

check, get_checks

Methods included from CheckSupport

#_run_checks!

Constructor Details

#initialize(rows, index, stats, options, query, result) ⇒ Checks

Returns a new instance of Checks.



9
10
11
12
13
14
15
16
17
18
# File 'lib/shiba/explain/checks.rb', line 9

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

Instance Attribute Details

#costObject (readonly)

Returns the value of attribute cost.



20
21
22
# File 'lib/shiba/explain/checks.rb', line 20

def cost
  @cost
end

Instance Method Details

#add_message(tag, extra = {}) ⇒ Object



30
31
32
# File 'lib/shiba/explain/checks.rb', line 30

def add_message(tag, extra = {})
  @result.messages << { tag: tag, table_size: table_size, table: table }.merge(extra)
end

#check_derivedObject



55
56
57
58
59
60
61
# File 'lib/shiba/explain/checks.rb', line 55

def check_derived
  if table =~ /<derived.*?>/
    # select count(*) from ( select 1 from foo where blah )
    add_message('derived_table', size: nil)
    @cost = 0
  end
end

#check_index_walkObject

check :check_index_walk disabling this one for now, it’s not quite good enough and has a high false-negative rate.



90
91
92
93
94
95
# File 'lib/shiba/explain/checks.rb', line 90

def check_index_walk
  if first['index_walk']
    @cost = limit
    add_message("index_walk")
  end
end

#check_joinObject



77
78
79
80
81
82
83
84
85
# File 'lib/shiba/explain/checks.rb', line 77

def check_join
  if @row['join_ref']
    @access_type.sub!("access_type", "join_type")
    # TODO MAYBE: are multiple-table joins possible?  or does it just ref one table?
    ref = @row['join_ref'].find { |r| r != 'const' }
    table = ref.split('.')[1]
    @tbl_message['join_to'] = table
  end
end

#check_key_sizeObject



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/shiba/explain/checks.rb', line 98

def check_key_size
  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
    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 key_size.nil?
    key_size = 1
  end

  if @row['join_ref']
    # 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.
    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 *= key_size
  else
    rows_read = key_size
    @result.result_size += key_size
  end

  @cost = Shiba::Explain::COST_PER_ROW_READ * rows_read

  # pin fully missed indexes to a 'low' threshold
  if @access_type == 'access_type_tablescan'
    @cost = [0.01, @cost].max
  end

  @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

#check_simple_table_scanObject



45
46
47
48
49
50
51
# File 'lib/shiba/explain/checks.rb', line 45

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

#run_checks!Object



147
148
149
150
151
# File 'lib/shiba/explain/checks.rb', line 147

def run_checks!
  _run_checks! do
    :stop if @cost
  end
end

#simple_table_scan?Boolean

TODO: need to parse SQL here I think

Returns:

  • (Boolean)


35
36
37
38
39
40
# File 'lib/shiba/explain/checks.rb', line 35

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

#tableObject



22
23
24
# File 'lib/shiba/explain/checks.rb', line 22

def table
  @row['table']
end

#table_sizeObject



26
27
28
# File 'lib/shiba/explain/checks.rb', line 26

def table_size
  @stats.table_count(table)
end

#tag_query_typeObject



64
65
66
67
68
69
70
71
72
73
74
# File 'lib/shiba/explain/checks.rb', line 64

def tag_query_type
  @access_type = @row['access_type']

  if @access_type.nil?
    @cost = 0
    return
  end

  @access_type = 'tablescan' if @access_type == 'ALL'
  @access_type = "access_type_" + @access_type
end