This comes up once in a while. Just because DBMS x has a Feature ABC, doesn't mean it's the same as Feature ABC on DBMS y.
Postgres materialized views barely do anything. I was so excited when they were announced [version 9.3 in 2013] until I actually looked into them. They are basically something I could implement myself in like 30 minutes with a few pl/pgsql functions (and maybe a table to store the necessary metadata). They just create a table based on a given query and you can refresh the whole thing on demand (like a truncate/insert). Then you can query the materialized view like any other table. If you want to refresh them automatically, you attach the refresh call to some cron job or perhaps in triggers on all dependencies (creating a mess IMO).
Oracle materialized views can do some pretty great stuff:
- Automatically refresh: You don't need to manually call any refresh function (although you CAN if you want to).
- Refresh on a schedule - Oracle has a built-in scheduler and materialized views are integrated with that, so I can say when I create my MV to "refresh every night at 2am" and it just works. Not a massive selling point, but it's convenient not to need some external scheduler such as cron.
- Refresh when dependencies (base tables) are updated: Say I create a MV based on a query such as -
SELECT x, y, z FROM sales JOIN country ... WHERE country.id IN (...)
- now whenever an INSERT/UPDATE/DELETE is run onsales
orcountry
, the MV is automatically updated. No call to any refresh function anywhere is necessary. But it's smart about it too - in this case since I'm only concerned about a specific list of countries (in my where clause), if I update a row having a country.id NOT in that in-list, no refresh on the MV will be triggered. So basically it only refreshes when it has to. Your mileage may vary in complex use cases.
- Query rewrite aka use an MV as an automagic index:
This lets you create a MV as sort of an index. The query doesn't need to
know about the MV's existence, but if the MV can answer the question
the query is asking, the query optimizer automatically reroutes the
query to the MV. Example: An application is issuing the query
SELECT sum(qty*price) FROM sales
but it's gotten pretty slow as it's recomputing that total for millions of sales every time it is run. I can create a MVsales_mv
on that exact query, and the query rewrite feature will automatically take the query fromsales
tosales_mv
without any modification of the query or application being necessary. Rewrite can even work in many cases where the MV's query isn't an exact match. For example if I modify the above query to GROUP BY state, the MV now has each sum(qty*price) per state. If I then run a query likeSELECT sum(qty*price) FROM sales WHERE state = 'CA'
, the query rewrite feature is smart enough to know that the MV is a superset of the data I requested, and it knows which subset of the MV can be used to fetch my desired results.- I can even provide the MV with dimension metadata
that can help the query optimizer in understanding when rewrite can be
applied. If I have that same sales query but broken down by month - so
something like:
SELECT sum(qty*price), month_num FROM sales group by month_num
- I can create a dimension that declares that months roll up in to quarters and which ones. So now if I ask the databaseSELECT sum(qty*price) FROM sales where quarter='Q2'
- if I've defined my dimensions properly the query optimizer can figure out that Q2 can be determined by adding up the totals frommonth_num
s 4, 5 and 6 in the MV - a minimal amount of work compared to summing from the detail level up - and it finds me my answer that way.
- I can even provide the MV with dimension metadata
that can help the query optimizer in understanding when rewrite can be
applied. If I have that same sales query but broken down by month - so
something like:
- "Fast" refresh: (Oracle calls it "fast" although I
prefer the term incremental refresh.) Say that sales MV refresh takes 30
seconds because there's a lot of data. If we have a trickle-in of about
a sale per minute, we're spending a lot of time and energy refreshing
this whole thing repeatedly - we spend 30 seconds of every minute
refreshing. But imagine if you as a human being had the job of keeping
track of a sales total on paper. Throughout the day, a salesperson yells
at you about a new sale, "We just got another sale of $100!" - you
wouldn't recompute your entire sales total from the entire history every
single time; rather you'd take your running total - let's say it's
$10,000 - and simply add $100 to it - so now it's $10,100. So, that's
analogous to how "fast refresh" works - just apply the increments. This
feature can be pretty huge in speeding up refreshes.
- One of many, many use cases: Multi-row constraints:
If you've ever wished you could apply some validation across multiple
rows in your table, you might be able to accomplish that with
materialized views. Since you can add check constraints on a MV, you
could define an MV to run some aggregation, and place a constraint on
the result of that aggregation. For example, you have a table
representing retirement fund allocations. Each customer's allocations
must add up to 100%. So you could create a MV on the query
SELECT SUM(percent_allocated) AS total_allocated, customer_id FROM allocation_table GROUP BY customer_id
-- add a check constraint that total_allocated must equal 100 and now no bad data can ever creep intoallocation_table
. Now this may be too slow and clunky if the full MV query needed to be refreshed every time the base tables are updated, but again...fast refresh!
- One of many, many use cases: Multi-row constraints:
If you've ever wished you could apply some validation across multiple
rows in your table, you might be able to accomplish that with
materialized views. Since you can add check constraints on a MV, you
could define an MV to run some aggregation, and place a constraint on
the result of that aggregation. For example, you have a table
representing retirement fund allocations. Each customer's allocations
must add up to 100%. So you could create a MV on the query
- Oracle also lets you choose to defer applying the "deltas" from a fast refresh to read time
of the materialized views. So if I would rather not slow down the
writes to the base tables, by dealing with MV refresh at that time, I
can tell Oracle to automatically queue up those changes and hold off on
applying them to the materialized view until the time at which I
actually read from it. So it's a tradeoff - if I have a large reporting
query backed by MV, the user might not mind if I add to it, let's say,
an average of half a second to it at read time. That may be better than
adding a few milliseconds to every write to the underlying
tables, if I have tight business constraints on the writes. So this
feature allows me to do that, and it's easy and declarative - type a few
magic words and it just works.
- Deferring applying deltas analogy: to continue with
the total sales on paper analogy: this is like keeping track of that
new $100 sale on a separate paper (let's call it the delta log) that
lists each new sale. Maybe you're so busy you don't have the mental
capacity to perform addition constantly. Another $100 sale comes in, so
you jot "$100" on the delta log. A $200 sale comes in, you write $200. A
$1042.59 sale comes in...etc. Now your manager asks, "Hey I need the
current total now please." So at THAT point, you catch up on the math:
you add 10,000 + 100 + 200 + 1042.59 = 11,342.59, the new running total.
Manager thanks you, you toss out the delta paper and start with a new
one, repeat.
- Caching joins: Now think about using fast refresh in conjunction with query write - that's a pretty powerful indexing feature. You could use it to precompute joins. (Everybody complains about how JOINS are slow and painful in SQL - Oracle actually has multiple solutions to pre-join tables. MVs are one of them.) If I have a billion WIDGETs, each of which was made in a FACTORY (linked to by WIDGET.FACTORY_ID), maybe queries that join the two are getting slow. So I can precompute the joins and query rewrite will potentially be able to use the MV for my slow queries, without anyone having to modify a query. And whenever a WIDGET or FACTORY is updated/inserted/deleted, fast refresh applies only the deltas so the whole thing isn't refreshed.
- So many potential use cases here. Sometimes I see the argument that Postgres has partial indexes,
and Oracle doesn't, so isn't Oracle lame? For context, a partial index
lets you create an index with a where clause to pre-filter out a bunch
of data you don't care about for the scope of a query/queries. So for
example
CREATE INDEX my_index ON order(id, order_date, etc.) WHERE status = 'ACTIVE';
Now if you have 99% inactive orders at any given time, the index can potentially be used to search the active ones more quickly. Great PG feature btw, but with Oracle you can accomplish essentially same thing by creating a MV:CREATE MATERIALIZED VIEW ... SELECT id, order_date, etc. WHERE status = 'ACTIVE'
just make sure you enable fast refresh and query rewrite and it's pretty much the same thing. (You can even define the MV as "index organized" to make it physically shaped like a B*Tree index, rather than a heap table, to make it even closer to "the same thing.") Sure you could argue in this use case, the Oracle solution is more complex, but the point is this one feature can be applied in tons of use cases - I could come up with some all day. :)
- Deferring applying deltas analogy: to continue with
the total sales on paper analogy: this is like keeping track of that
new $100 sale on a separate paper (let's call it the delta log) that
lists each new sale. Maybe you're so busy you don't have the mental
capacity to perform addition constantly. Another $100 sale comes in, so
you jot "$100" on the delta log. A $200 sale comes in, you write $200. A
$1042.59 sale comes in...etc. Now your manager asks, "Hey I need the
current total now please." So at THAT point, you catch up on the math:
you add 10,000 + 100 + 200 + 1042.59 = 11,342.59, the new running total.
Manager thanks you, you toss out the delta paper and start with a new
one, repeat.
- Stale tolerance: this goes hand and hand with query
rewrite. If I am using query rewrite, I can define whether staleness is
tolerated. So imagine I am running
SELECT sum(qty*price) FROM sales
again, and I've defined it to refresh on a nightly schedule (not automatically when dependencies are updated). This means at any given moment during the day, the MV's contents may be "stale" if updates/inserts/deletes have happened since refresh. So I can decide on a per-session basis (if desired) if I'd like query rewrite to route me to the MV when the MV is stale. If I need real-time data, run the normal sum against the guaranteed-to-be-up-to-date sales table instead even though it may be slower. If last night's data is good enough, I declare that stale tolerated is OK and I get to use the MV that way. - Synchronized Materialized View Groups: Basically you can group and refresh many materialized views together to guarantee that they are in sync. Think atomicity.
- Plays well with partitions/subpartitions.
There's more, but that's the gist.
All that said - there's a neat extension that at least adds incremental updates to Postgres MVs. It's called pg_imv: https://github.com/sraoss/pg_ivm
1 comment:
Note I have discovered there is a Postgres extension you can install that will handle automatic refreshes when a base table's data is modified, AND handles incremental refresh. Very promising! https://github.com/sraoss/pg_ivm
This was originally planned to be a core feature in Postgres - as you can see in this enormous thread: https://www.postgresql.org/message-id/flat/20181227215726.4d166b4874f8983a641123f5@sraoss.co.jp - but it looks like the discussion on the subject was dropped. I may look into whether I can help with this effort because this is pretty huge IMO!
Post a Comment