Vow, Gert-Jan,
testing shows that you proved to be absolutely right in each and every
respect!
1) indexed view table (and index body, defined on the view) are
repopulated automatically after each insert into the underlying table.
2) the underlying table is completely locked (even for selects) for
duration of all processes, connected with population of both table and
indexed view structures (i.e. even attempt to select from table will
wait for completion of repopulation of the index(es) on the view, not
only update of the table itself).
So I was not right: indexed view (i.e. index body) stays always
populated, the process is automatic and reacts on any change in
underlying table(s).
I.e. the first select from indexed view takes the same time, as
subsequent.
Summary: indexed views are great for reporting, DSS, data marts, but
awfull for OLTP, speed of table update,insert and select will be bad
(any attempt to SELECT from tables will be delayed until repopulation
of all indexes in the views is completed) (that is if we do not
specify dirty read hints in select).
I have modified your script:
Step one:
CREATE TABLE MainTable
(Status char(10) not null
,Total int not null
)
Step two: create secondary table (will be used in data population):
select * into mtcopy from maintable where 1=2
-- populate secondary table:
INSERT INTO mtcopy VALUES ('Test',1)
INSERT INTO mtcopy VALUES ('Test',2)
INSERT INTO mtcopy VALUES ('Test',3)
INSERT INTO mtcopy VALUES ('Test',4)
INSERT INTO mtcopy VALUES ('Test',5)
INSERT INTO mtcopy VALUES ('Test',6)
INSERT INTO mtcopy VALUES ('Test',7)
INSERT INTO mtcopy VALUES ('Test',8)
INSERT INTO mtcopy VALUES ('Test',9)
INSERT INTO mtcopy VALUES ('Finished',99)
Step 3: create script for population of the mainTable with 100,000
rows at a time:
-- we use cross-product from 5 named instances of the secondary table
-- 10 in power of 5 = 100,000 rows:
SET STATISTICS IO ON
go
insert into maintable
select m1.status, m1.total from
mtcopy m1, mtcopy m2, mtcopy m3, mtcopy m4 , mtcopy m5
Step 4: do population of mainTable (indexed view does not exist yet):
Results:
Table 'MainTable'. Scan count 0, logical reads 100001, physical reads
0, read-ahead reads 0.
Table 'mtcopy'. Scan count 5, logical reads 5, physical reads 0,
read-ahead reads 0.
(100000 row(s) affected)
time: 15 seconds.
Populate second time: 15 seconds.
Step 5: Define view:
CREATE VIEW IView with SCHEMABINDING
AS
SELECT Status, SUM(Total) AS GrandTotal, COUNT_BIG(*) AS CountBig
FROM dbo.MainTable
GROUP BY Status
Step 6: repeat population with 100,000 rows.
Result: time 15 seconds, same statistics.
That is understandable, the view exists, but it does not have any
indexes,
no body yet.
Step 7: create index on view:
CREATE UNIQUE CLUSTERED INDEX IX_IView_Status ON IView(Status)
Takes 10 seconds ("the body" of the indexed view is being created).
Step 8: add 100,000 rows: results:
Table 'IView'. Scan count 3, logical reads 10, physical reads 0,
read-ahead reads 0.
Table 'MainTable'. Scan count 0, logical reads 193781, physical reads
0, read-ahead reads 0.
Table 'mtcopy'. Scan count 5, logical reads 5, physical reads 0,
read-ahead reads 0.
(100000 row(s) affected)
Time: 31 seconds.
Now finally: both the underlying table AND the index body of the view
had been repopulated (15 seconds for each=total 30 seconds).
We see that the view is being treated like a "Table IView".
Step 9: select * from iview
results:
Status GrandTotal CountBig
---------- ----------- --------------------
Finished 3960000 40000
Test 1800000 360000
(2 row(s) affected)
time: 12 seconds
Index is not used, because we did not specified the hint. Indexed view
was used like regular view, 12 seconds was used for aggregations
(400,000 rows for now).
Step 10: SELECT Status, GrandTotal FROM IView with (NOEXPAND)
Results: the same as previous, time 0 seconds.
Finally, the index WAS used (execution plan shows "clustured indexed
scan", 100%).
Step 11: Add ONE ROW into underlying table:
insert into maintable values ('test_add', 0)
results:
Table 'IView'. Scan count 2, logical reads 6, physical reads 0,
read-ahead reads 0.
Table 'MainTable'. Scan count 0, logical reads 2, physical reads 0,
read-ahead reads 0.
(1 row(s) affected)
time: 0 seconds.
Most probably, the aggregations had be recalculated on the view, but
because the clustered index exists, it did not take any time.
Execution plan shows a table insert actions (using clustered index),
then it shows sort (why table is reordered?), then finally it shows
"Clustered Index Update", rowcount=1 on IView.IX_IView_Status - the
index body itself is being updated.
So we see that on insert of the row into underlying table the index
body WAS updated.
Step 12: While 100,000 rows being inserted from one window, attempt to
insert one row into the table, from another window:
result: 31 seconds. (insert operation effectively blocking other
insert into table, making it to wait).
Step 13: While 100,000 rows being inserted from one window, attempt to
SELECT aggregates from , from another window (using as regular view):
SELECT Status, GrandTotal FROM IView
results: correct (500,000 rows), time: 37 seconds.
Insert operation effectively blocks ANY select attempt to the
underlying table.
Select has to wait for insert into table to finish (first 15 seconds),
then
for insert and rebuild of the index body to finish (another 15
seconds), since it is part of the transaction, then to build the
aggregation (7 seconds).
Step 14: while 100,000 rows being inserted from one window, attempt to
select using the indexed view:
SELECT Status, GrandTotal FROM IView with (NOEXPAND)
results: correct (600,000 rows),
time: 31 seconds. (BOTH table body and index body are locked until
completion of all operations).
(however if we shall repeat:
SELECT Status, GrandTotal FROM IView with (NOEXPAND)
the same results will be produced in 0 seconds, tables are not locked
anymore).
Step 15: while 100,000 rows being inserted from one window, attempt to
select count from underlying table:
select count(*) from maintable
result: correct (700,000), time: 31 seconds.
The table itself is locked, untill the view index body is rebuilt!
Step 16: drop index on the view:
drop index IView.IX_IView_Status
while 100,000 rows being inserted from one window, attempt to select
count from underlying table:
select count(*) from maintable
result: correct (700,000 rows), time: 17 seconds.
Waits for 15 seconds for insert, then for 2 seconds for count result.
Index body does not exist any more.
Step 17: Indentical result will be for view:
SELECT Status, GrandTotal FROM IView
27 seconds (15 seconds to complete insert, 12 seconds for
aggregations).
Step 18: attempt to use indexed view is not possible:
SELECT Status, GrandTotal FROM IView with (NOEXPAND)
compiler message:
Hint 'noexpand' on object 'IView' is invalid.
(indexed does not exist any more, indexed view can be used only as
regular view).
So thank you so much, the truth is reached.
Alexei