lib/ecto_mysql_extras/queries/unused_indexes.ex

defmodule EctoMySQLExtras.UnusedIndexes do
  @moduledoc """
  Query all unused indexes.

  Data is retrieved from the `performance_schema` database and the `table_io_waits_summary_by_index_usage` table.
  The database should be running for a while since unused indexes are tracked based on IO activity.
  An index is considered unused when it has 0 hits and almost unused when it has 50 hits or less. The latter can however
  be configured by passing: `min_hits` as an argument. Also indexes of small tables (less than 5 pages) are excluded.
  Primary keys are excluded from the query.
  """
  @behaviour EctoMySQLExtras

  def info do
    %{
      title: "Unused and almost unused indexes",
      order_by: [index_hits: :DESC],
      args: [:min_hits],
      default_args: [min_hits: 50],
      columns: [
        %{name: :schema, type: :string},
        %{name: :table, type: :string},
        %{name: :index, type: :string},
        %{name: :pages, type: :integer},
        %{name: :size, type: :bytes},
        %{name: :index_hits, type: :integer}
      ]
    }
  end

  def query(args \\ []) do
    """
    /* ECTO_MYSQL_EXTRAS: #{info().title} */

    SELECT
      u.OBJECT_SCHEMA AS `schema`,
      u.OBJECT_NAME AS `table`,
      u.INDEX_NAME AS `index`,
      s.`pages`,
      s.`size`,
      u.COUNT_STAR AS `index_hits`
    FROM performance_schema.table_io_waits_summary_by_index_usage u
    LEFT JOIN (
    	SELECT
    		table_name,
    		index_name,
      		CAST(SUM(stat_value) AS UNSIGNED) AS `pages`,
      		CAST(ROUND(SUM(stat_value)*@@innodb_page_size) AS UNSIGNED) AS `size`
    	FROM mysql.innodb_index_stats
    	WHERE database_name = DATABASE()
    	AND stat_name = 'size'
    	GROUP BY table_name, index_name
    ) s ON u.OBJECT_NAME = s.table_name AND u.INDEX_NAME = s.index_name
    WHERE u.OBJECT_SCHEMA = DATABASE()
    AND u.COUNT_STAR < #{args[:min_hits]}
    AND s.`pages` > 5
    AND s.index_name <> 'PRIMARY'
    ORDER BY `index_hits` DESC;
    """
  end
end