All Products
Search
Document Center

AnalyticDB:Query rewrite of materialized views

Last Updated:Mar 28, 2026

Query rewrite lets AnalyticDB for MySQL automatically redirect queries to materialized views—without any changes to your SQL. When a query matches a materialized view, the engine reads precomputed results instead of scanning base tables, which can significantly reduce query latency.

Prerequisites

Before you begin, make sure you have:

  • An AnalyticDB for MySQL cluster running V3.1.4.0 or later

    To check the minor version of a Data Lakehouse Edition cluster, run SELECT adb_version();. To upgrade, contact technical support. For Data Warehouse Edition clusters, see Update the minor version of a cluster.
  • The following permissions on the relevant databases or tables:

    PermissionRequired for
    CREATECreating materialized views
    INSERTRefreshing materialized views
    SELECTQuerying the base tables and columns referenced by a materialized view
    Refresh via 127.0.0.1 or '%'Configuring auto-refresh on materialized views you own

How it works

AnalyticDB for MySQL compares each incoming query against all materialized views that have query rewrite enabled. If a match is found, the engine rewrites the query to read from the materialized view instead of the base tables. Two matching strategies are used, applied in the following order:

  • Exact match rewrite — when the query structure is identical to the materialized view definition. This is the simpler strategy with fewer restrictions.

  • Advanced query rewrite — when the structures differ. The engine applies rewrite rules (FILTER, JOIN, AGGREGATION, AGGREGATION ROLLUP, SUBQUERIES, QUERY PARTIAL, UNION) to determine whether the materialized view contains enough data to answer the query or part of it. Different subqueries in the same statement can match different materialized views.

All query rewrites operate at the STALE_TOLERATED level: the engine rewrites a query even if the materialized view contains stale data that hasn't been synced from the base tables. This maximizes rewrite coverage but means results may not reflect the latest inserts or updates. Refresh your materialized views before running latency-sensitive queries. For details, see Configure full refresh for materialized views.

AnalyticDB for MySQL支持如下改写级别:

  • TRUSTED:仅使用已刷新到最新数据的物化视图来自动改写查询,即物化视图存储的数据与基表数据相同。

  • STALE_TOLERATED:会对所有开启改写的物化视图进行查询改写,即使物化视图中的数据不是最新的,改写结果可能无法反映物化视图基表的最新更改。

Enable query rewrite

Enable query rewrite in one of two ways:

Disable query rewrite

Disable query rewrite in one of two ways:

  • For a specific materialized view:

    ALTER MATERIALIZED VIEW <mv_name> DISABLE QUERY REWRITE;
  • For a specific query, add a hint before the SELECT statement:

    /*+MV_QUERY_REWRITE_ENABLED=false*/
    SELECT ...

Verify that query rewrite is active

After enabling query rewrite, use EXPLAIN to confirm the engine is reading from the materialized view.

Example

  1. Create a materialized view with query rewrite enabled.

    CREATE MATERIALIZED VIEW adb_mv
    REFRESH START WITH now() + interval 1 day
    ENABLE QUERY REWRITE
    AS
    SELECT course_id, course_name, max(course_grade) AS max_grade FROM tb_courses;
  2. After enabling query rewrite, run EXPLAIN on the query.

    EXPLAIN SELECT course_id, course_name, max(course_grade) AS max_grade FROM tb_courses;
  3. Check the execution plan. When query rewrite is active, the TableScan row shows the materialized view name (adb_mv), not the base table (tb_courses).

    +---------------+
    | Plan Summary  |
    +---------------+
     1- Output[ Query plan ] {Est rowCount: 1.0}
     2    -> Exchange[GATHER] {Est rowCount: 1.0}
     3        - TableScan {table: adb_mv, Est rowCount: 1.0}

    If the plan still shows TableScan {table: tb_courses, ...}, query rewrite did not activate. Check the Troubleshooting section.

Rewrite range

The following examples demonstrate each rewrite rule supported by the advanced query rewrite method. All examples use the same four tables:

CREATE TABLE part (
  partkey INTEGER NOT NULL,
  name VARCHAR(55) NOT NULL,
  type VARCHAR(25) NOT NULL
);

CREATE TABLE lineitem (
  orderkey BIGINT,
  partkey BIGINT NOT NULL,
  suppkey BIGINT NOT NULL,
  extendedprice DOUBLE NOT NULL,
  discount DOUBLE NOT NULL,
  returnflag CHAR(1) NOT NULL,
  linestatus CHAR(1) NOT NULL,
  shipdate DATE NOT NULL,
  shipmode VARCHAR(25) NOT NULL,
  commitdate DATE NOT NULL,
  receiptdate DATE NOT NULL
);

CREATE TABLE orders (
  orderkey BIGINT PRIMARY KEY,
  custkey BIGINT NOT NULL,
  orderstatus VARCHAR(1) NOT NULL,
  totalprice DOUBLE NOT NULL,
  orderdate DATE NOT NULL
);

CREATE TABLE partsupp (
  partkey INTEGER NOT NULL PRIMARY KEY,
  suppkey INTEGER NOT NULL,
  availqty INTEGER NOT NULL,
  supplycost DECIMAL(15,2) NOT NULL
);

Exact match rewrite

When the query structure is identical to the materialized view definition, the engine rewrites the query directly.

Query

SELECT
  l.returnflag,
  l.linestatus,
  SUM(l.extendedprice * (1 - l.discount)),
  COUNT(*) AS count_order
FROM lineitem AS l
GROUP BY l.returnflag, l.linestatus;

Materialized view

CREATE MATERIALIZED VIEW mv0
REFRESH NEXT now() + interval 1 day
ENABLE QUERY REWRITE
AS
SELECT
  l.returnflag,
  l.linestatus,
  SUM(l.extendedprice * (1 - l.discount)) AS sum_disc_price,
  COUNT(*) AS count_order
FROM lineitem AS l
GROUP BY l.returnflag, l.linestatus;

Rewritten query

SELECT returnflag, linestatus, sum_disc_price, count_order
FROM mv0;

Advanced query rewrite

FILTER

When the query predicate is narrower than the materialized view's predicate, the engine adds a WHERE clause to the materialized view scan to apply the missing filter.

Query

SELECT
  l.shipmode,
  l.extendedprice * (1 - l.discount) AS disc_price
FROM orders AS o, lineitem AS l
WHERE o.orderkey = l.orderkey
  AND l.shipmode IN ('REG AIR', 'TRUCK')
  AND l.commitdate < l.receiptdate
  AND l.shipdate < l.commitdate;

Materialized view

CREATE MATERIALIZED VIEW mv1
REFRESH NEXT now() + interval 1 day
ENABLE QUERY REWRITE
AS
SELECT
  l.shipmode,
  l.extendedprice,
  l.discount
FROM orders AS o, lineitem AS l
WHERE o.orderkey = l.orderkey
  AND l.commitdate < l.receiptdate
  AND l.shipdate < l.commitdate;

Rewritten query

SELECT
  shipmode,
  extendedprice * (1 - discount) AS disc_price,
  discount
FROM mv1
WHERE shipmode IN ('REG AIR', 'TRUCK');

JOIN

When the query and the materialized view have different join relationships, the engine derives the required join from the materialized view. For example, a materialized view with an outer join can satisfy a query that needs an inner join by filtering out null rows.

Supported join types: inner join, outer join, left join, and right join.

Query

SELECT
  p.type,
  p.partkey,
  ps.suppkey
FROM part AS p, partsupp AS ps
WHERE p.partkey = ps.partkey
  AND p.type NOT LIKE 'MEDIUM POLISHED%';

Materialized view

CREATE MATERIALIZED VIEW mv2
REFRESH NEXT now() + INTERVAL 1 day
ENABLE QUERY REWRITE
AS
SELECT
  p.type,
  p.partkey,
  ps.suppkey
FROM partsupp AS ps
INNER JOIN part AS p ON p.partkey = ps.partkey
WHERE p.type NOT LIKE 'MEDIUM POLISHED%';

Rewritten query

SELECT type, partkey, suppkey
FROM mv2;

AGGREGATION

When the query or the materialized view uses different GROUP BY clauses or aggregate functions, the engine constructs the same aggregate function from the materialized view using the AGGREGATION rule.

Query

SELECT
  l.returnflag,
  l.linestatus,
  SUM(l.extendedprice * (1 - l.discount)) AS sum_disc_price
FROM lineitem AS l
GROUP BY l.returnflag, l.linestatus;

Materialized view

CREATE MATERIALIZED VIEW mv3
REFRESH NEXT now() + INTERVAL 1 day
ENABLE QUERY REWRITE
AS
SELECT
  l.returnflag,
  l.linestatus,
  SUM(l.extendedprice * (1 - l.discount)) AS sum_disc_price,
  COUNT(*) AS count_order
FROM lineitem AS l
GROUP BY l.returnflag, l.linestatus;

Rewritten query

SELECT returnflag, linestatus, sum_disc_price, count_order
FROM mv3;

AGGREGATION ROLLUP

When the query groups by a subset of the materialized view's GROUP BY fields, the engine rolls up the precomputed aggregates.

Query

SELECT
  l.returnflag,
  SUM(l.extendedprice * (1 - l.discount)) AS sum_disc_price,
  COUNT(*) AS count_order
FROM lineitem AS l
WHERE l.returnflag = 'R'
GROUP BY l.returnflag;

Materialized view

CREATE MATERIALIZED VIEW mv4
REFRESH NEXT now() + INTERVAL 1 day
ENABLE QUERY REWRITE
AS
SELECT
  l.returnflag,
  l.linestatus,
  SUM(l.extendedprice * (1 - l.discount)) AS sum_disc_price,
  COUNT(*) AS count_order
FROM lineitem AS l
GROUP BY l.returnflag, l.linestatus;

Rewritten query

SELECT
  returnflag,
  linestatus,
  sum_disc_price,
  count_order
FROM mv4
WHERE returnflag = 'R'
GROUP BY returnflag;

SUBQUERIES

When the query uses a subquery in place of a base table, the engine checks whether the materialized view covers the full table and pushes the subquery's filter into the rewritten scan.

Query

SELECT
  p.type,
  p.partkey,
  ps.suppkey
FROM part AS p,
  (SELECT * FROM partsupp WHERE suppkey > 10) ps
WHERE p.partkey = ps.partkey;

Materialized view

CREATE MATERIALIZED VIEW mv5
REFRESH NEXT now() + INTERVAL 1 day
ENABLE QUERY REWRITE
AS
SELECT
  p.type,
  p.partkey,
  ps.suppkey
FROM part AS p, partsupp AS ps
WHERE p.partkey = ps.partkey;

Rewritten query

SELECT type, partkey, suppkey
FROM mv5
WHERE suppkey > 10;

QUERY PARTIAL

When the query references a table that the materialized view doesn't cover, the engine joins the materialized view with the missing table.

Query

SELECT
  p.type,
  p.partkey,
  ps.suppkey
FROM part AS p, partsupp AS ps
WHERE p.partkey = ps.partkey
  AND p.type NOT LIKE 'MEDIUM POLISHED%';

Materialized view

CREATE MATERIALIZED VIEW mv6
REFRESH NEXT now() + INTERVAL 1 day
ENABLE QUERY REWRITE
AS
SELECT
  p.type,
  p.partkey
FROM part AS p
WHERE p.type NOT LIKE 'MEDIUM POLISHED%';

Rewritten query

SELECT
  mv6.type,
  mv6.partkey,
  ps.suppkey
FROM mv6, partsupp AS ps
WHERE mv6.partkey = ps.partkey;

UNION

When the materialized view covers only part of the query's date or value range, the engine retrieves the covered portion from the materialized view and fetches the remaining rows directly from the base table, then combines the results with UNION ALL.

Query

SELECT
  l.linestatus,
  COUNT(*) AS count_order
FROM lineitem AS l
WHERE l.shipdate >= DATE '1998-01-01'
GROUP BY l.linestatus;

Materialized view (covers shipdate >= 2000-01-01 only)

CREATE MATERIALIZED VIEW mv7
REFRESH NEXT now() + interval 1 day
ENABLE QUERY REWRITE
AS
SELECT
  l.linestatus,
  COUNT(*) AS count_order
FROM lineitem AS l
WHERE l.shipdate >= DATE '2000-01-01'
GROUP BY l.linestatus;

Rewritten query

SELECT linestatus, count_order
FROM (
    SELECT linestatus, count_order
    FROM mv7
  UNION ALL
    SELECT
      l.linestatus,
      COUNT(*) AS count_order
    FROM lineitem AS l
    WHERE l.shipdate >= DATE '1998-01-01' AND l.shipdate < DATE '1998-01-01'
    GROUP BY l.linestatus
)
GROUP BY linestatus;

Limitations

Exact match rewrite

The exact match rewrite method does not activate if the materialized view contains:

  • Nondeterministic functions: NOW, CURRENT_TIMESTAMP, RANDOM

  • User-defined functions (UDFs)

Advanced query rewrite

The advanced query rewrite method does not activate if the materialized view contains any of the following:

  • ORDER BY, LIMIT, or OFFSET clause

  • UNION or UNION ALL clause

  • GROUPING SETS, CUBE, or ROLLUP in a GROUP BY clause

  • Window functions

  • FULL OUTER JOIN

  • System tables

  • Correlated subqueries

  • Nondeterministic functions: NOW, CURRENT_TIMESTAMP, RANDOM

  • UDFs

  • HAVING clause

  • SELF JOIN

Statement types where query rewrite never activates

Query rewrite does not apply to queries embedded in the following statement types, regardless of the materialized view definition:

  • CREATE TABLE AS SELECT

  • INSERT INTO SELECT

  • INSERT OVERWRITE SELECT

  • REPLACE INTO SELECT

  • DELETE or UPDATE

Single-table queries without filters or aggregates

Query rewrite does not activate for single-table queries that have no filter conditions and no aggregate functions.

Troubleshooting

Query rewrite isn't activating after I create a materialized view.

Start with the most common causes:

  1. Query rewrite is not enabled on the materialized view. Run ALTER MATERIALIZED VIEW <mv_name> ENABLE QUERY REWRITE; and try again.

  2. The materialized view hits a limit. Review the Limitations section and check whether your materialized view definition includes any unsupported clause or function.

  3. Missing SELECT permission on the materialized view. Grant the required permission to the account running the query:

    GRANT SELECT ON <database>.<mv_name> TO '<account>';

    For details, see the Required permissions section of the Query data from a materialized view topic.

  4. 物化视图是否过期或正在刷新中。您可以通过REFRESH MATERIALIZED VIEW <mv_name>语句手动刷新一次物化视图,更新详情,请参见全量刷新物化视图

  5. 物化视图是否包含查询涉及的所有列,若未包含所有列,自动改写不会生效。建议确认查询所涉及的所有列后,再重新创建视图。创建视图的语句,请参见创建物化视图

What's next