def test_explain_queries(self):
        res = list(explain_queries(self.connection, self.queries))
        tables_used = [item[1] for item in res]

        print(res, tables_used)

        assert '0020_big_table' in tables_used
def check_queries_not_using_indices(database, queries):
    """
    :type database  indexdigest.database.Database
    :type queries list[str]
    :rtype: list[LinterEntry]
    """
    for (query, table_used, index_used,
         explain_row) in explain_queries(database, queries):
        # print(query, explain_row)

        # EXPLAIN can return no matching row in const table in Extra column.
        # Do not consider this query as not using an index. -- see #44
        if explain_row['Extra'] in [
                'Impossible WHERE noticed after reading const tables',
                'no matching row in const table', 'No tables used'
        ]:
            continue

        if index_used is None:
            context = OrderedDict()
            context['query'] = query

            # https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain-extra-information
            context['explain_extra'] = explain_row['Extra']
            context['explain_rows'] = explain_row['rows']
            context['explain_filtered'] = explain_row.get(
                'filtered')  # can be not set
            context['explain_possible_keys'] = explain_row['possible_keys']

            yield LinterEntry(
                linter_type='queries_not_using_index',
                table_name=table_used,
                message='"{}" query did not make use of any index'.format(
                    shorten_query(query)),
                context=context)
def check_full_table_scan(database, queries):
    """
    Full table scan

    An operation that requires reading the entire contents of a table, rather than just selected
    portions using an index. Typically performed either with small lookup tables, or in data
    warehousing situations with large tables where all available data is aggregated and analyzed.
    How frequently these operations occur, and the sizes of the tables relative to available memory,
    have implications for the algorithms used in query optimization and managing the buffer pool.

    :type database  indexdigest.database.Database
    :type queries list[str]
    :rtype: list[LinterEntry]
    """
    for (query, table_used, _, row) in explain_queries(database, queries):
        # The output from EXPLAIN shows ALL in the type column when
        # MySQL uses a full table scan to resolve a query.
        if row['type'] != 'ALL':
            continue

        context = OrderedDict()
        context['query'] = query
        context['explain_rows'] = int(row['rows'])  # we get string here when using MariaDB 10.5

        yield LinterEntry(linter_type='queries_using_full_table_scan', table_name=table_used,
                          message='"{}" query triggered full table scan'.
                          format(shorten_query(query)),
                          context=context)
示例#4
0
def check_not_used_indices(database, queries):
    """
    :type database  indexdigest.database.Database
    :type queries list[str]
    :rtype: list[LinterEntry]
    """
    logger = logging.getLogger(__name__)

    used_indices = defaultdict(list)

    # EXPLAIN each query
    for (query, table_used, index_used,
         _) in explain_queries(database, queries):
        if index_used is not None:
            logger.info("Query <%s> uses %s index on `%s` table", query,
                        index_used, table_used)
            used_indices[table_used].append(index_used)

    # analyze all tables used by the above queries
    # print(used_indices)
    for table_name, table_indices in used_indices.items():
        for index in database.get_table_indices(table_name):

            if index.name not in table_indices:
                yield LinterEntry(
                    linter_type='not_used_indices',
                    table_name=table_name,
                    message='"{}" index was not used by provided queries'.
                    format(index.name),
                    context={"not_used_index": str(index)})
def filter_explain_extra(database, queries, check):
    """
    Parse "Extra" column from EXPLAIN query results, e.g.

    "Using where; Using temporary; Using filesort"

    :type database  indexdigest.database.Database
    :type queries list[str]
    :type check str
    :rtype: list
    """
    for (query, table_used, _,
         explain_row) in explain_queries(database, queries):
        extra_parsed = str(explain_row['Extra']).split('; ')

        if check in extra_parsed:
            context = OrderedDict()
            context['query'] = query

            context['explain_extra'] = explain_row['Extra']
            context['explain_rows'] = int(
                explain_row['rows'])  # string when using MariaDB 10.5
            context['explain_filtered'] = explain_row.get(
                'filtered')  # can be not set
            context['explain_key'] = explain_row['key']

            yield (query, table_used, context)
def check_selects_with_like(database, queries):
    """
    :type database  indexdigest.database.Database
    :type queries list[str]
    :rtype: list[LinterEntry]
    """
    for (query, table_used, index_used, explain_row) in explain_queries(database, queries):
        if index_used is None and query_uses_leftmost_like(query):
            context = OrderedDict()
            context['query'] = query

            # https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain-extra-information
            context['explain_extra'] = explain_row['Extra']
            context['explain_rows'] = explain_row['rows']

            yield LinterEntry(linter_type='selects_with_like', table_name=table_used,
                              message='"{}" query uses LIKE with left-most wildcard'.
                              format(shorten_query(query)),
                              context=context)