7

Some Things To Consider Now ROWIDs Are Updated When Rows Migrate Part I (“More”)

 1 year ago
source link: https://richardfoote.wordpress.com/2023/02/22/some-things-to-consider-now-rowids-are-updated-when-rows-migrate-part-i-more/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Some Things To Consider Now ROWIDs Are Updated When Rows Migrate Part I (“More”) February 22, 2023

Posted by Richard Foote in 19c, Autonomous Database, Autonomous Transaction Processing, Changing ROWID, Migrated Rows, Oracle, Oracle 21c, Oracle Blog, Oracle Cloud, Oracle General, Oracle Indexes, Oracle19c, Pink Floyd, Richard's Blog.
trackback

more-pink-floyd.jpg?w=300&h=300

In my previous post, I discussed the obvious advantage of ROWIDs now being updated when rows migrate in an Oracle Autonomous Database, that being subsequent accesses to these rows via an index being more efficient.

However, there were likely reasons why Oracle has not historically updated ROWIDs on the fly in the past, so it’s worth exploring some of the possible side-effects of this new behaviour.

The most obvious issue will be for those applications that explicitly currently store ROWIDs, to enable the direct and very fast retrieval of such rows without having to read and access additional index blocks. If the ROWID can now suddenly change when a row is simply migrated, then of course these applications will no longer be guaranteed to be able to access these rows via the stored ROWIDs. Worse, it may now be possible for such applications to unknowingly fetch the wrong row, with the ROWID value now potentially associated with an entirely different row.

If this is a legitimate concern, then the remedy is simply to just NOT assign such tables the ENABLE ROW MOVEMENT attribute (which is disabled by default on a table) and the behaviour of migrated rows in association with ROWIDs will remain unchanged in Oracle Autonomous Databases. The risks here can be clearly and easily limited.

The other obvious disadvantage with ROWIDs being updated on the fly when a row migrates is in the additional costs associated with such Update statements in maintaining all the corresponding indexes.

As I discussed previously, these additional costs can be significant, especially if we have many indexes on a table.

To illustrate these extra costs, a simple example.

I’ll first start by creating and populating a table called BIG_ZIGGY (which at 100,000 rows is actually quite tiny, but it does have a number of columns) that does NOT have ENABLE ROW MOVEMENT set. The PCTFREE is set to 0 is ensure the rows are nicely packed in each block:

SQL> CREATE TABLE big_ziggy(id number, code1 number, code2 number, code3 number, code4 number, code5 number, code6 number, code7 number, code8 number, code9 number, code10 number, code11 number, code12 number, code13 number, code14 number, code15 number, code16 number, code17 number, code18 number, code19 number, code20 number, name varchar2(142)) PCTFREE 0;
Table BIG_ZIGGY created.
SQL> INSERT INTO big_ziggy SELECT rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, 'BOWIE' FROM dual CONNECT BY LEVEL <= 100000;
100,000 rows inserted.
SQL> commit;
Commit complete.
SQL> exec dbms_stats.gather_table_stats(ownname=> null, tabname=> 'BIG_ZIGGY');
PL/SQL procedure successfully completed.

As we’ve only inserted rows, there are currently no migrated rows:

SQL> analyze table big_ziggy compute statistics;
Table BIG_ZIGGY analyzed.
SQL> select table_name, num_rows, chain_cnt from user_tables where table_name='BIG_ZIGGY';
TABLE_NAME    NUM_ROWS    CHAIN_CNT
_____________ ___________ ____________
BIG_ZIGGY          100000            0

I’ll next create a whole bunch of indexes on many of these columns:

SQL> create index big_ziggy_id_i on big_ziggy(id);
Index BIG_ZIGGY_ID_I created.
SQL> create index big_ziggy_code1_i on big_ziggy(code1);
Index BIG_ZIGGY_CODE1_I created.
SQL> create index big_ziggy_code2_i on big_ziggy(code2);
Index BIG_ZIGGY_CODE2_I created.
SQL> create index big_ziggy_code3_i on big_ziggy(code3);
Index BIG_ZIGGY_CODE3_I created.
SQL> create index big_ziggy_code4_i on big_ziggy(code4);
Index BIG_ZIGGY_CODE4_I created.
SQL> create index big_ziggy_code5_i on big_ziggy(code5);
Index BIG_ZIGGY_CODE5_I created.
SQL> create index big_ziggy_code6_i on big_ziggy(code6);
Index BIG_ZIGGY_CODE6_I created.
SQL> create index big_ziggy_code7_i on big_ziggy(code7);
Index BIG_ZIGGY_CODE7_I created.
SQL> create index big_ziggy_code8_i on big_ziggy(code8);
Index BIG_ZIGGY_CODE8_I created.
SQL> create index big_ziggy_code9_i on big_ziggy(code9);
Index BIG_ZIGGY_CODE9_I created.
SQL> create index big_ziggy_code10_i on big_ziggy(code10);
Index BIG_ZIGGY_CODE10_I created.
SQL> create index big_ziggy_code11_i on big_ziggy(code11);
Index BIG_ZIGGY_CODE11_I created.
SQL> create index big_ziggy_code12_i on big_ziggy(code12);
Index BIG_ZIGGY_CODE12_I created.
SQL> create index big_ziggy_code13_i on big_ziggy(code13);
Index BIG_ZIGGY_CODE13_I created.
SQL> create index big_ziggy_code14_i on big_ziggy(code14);
Index BIG_ZIGGY_CODE14_I created.
SQL> create index big_ziggy_code15_i on big_ziggy(code15);
Index BIG_ZIGGY_CODE15_I created.
SQL> create index big_ziggy_code16_i on big_ziggy(code16);
Index BIG_ZIGGY_CODE16_I created.
SQL> create index big_ziggy_code17_i on big_ziggy(code17);
Index BIG_ZIGGY_CODE17_I created.
SQL> create index big_ziggy_code18_i on big_ziggy(code18);
Index BIG_ZIGGY_CODE18_I created.
SQL> create index big_ziggy_code19_i on big_ziggy(code19);
Index BIG_ZIGGY_CODE19_I created.
SQL> create index big_ziggy_code20_i on big_ziggy(code20);
Index BIG_ZIGGY_CODE20_I created.

I’ll now run an UPDATE statement, that will increase the row size and result in a number of row migrations:

SQL> update big_ziggy set name='THE RISE AND FALL OF ZIGGY STARDUST AND THE SPIDERS FROM MARS';
100,000 rows updated.
PLAN_TABLE_OUTPUT
____________________________________________________________________________________
SQL_ID 53xtnn8mmtwj5, child number 0
-------------------------------------
update big_ziggy set name='THE RISE AND FALL OF ZIGGY STARDUST AND THE
SPIDERS FROM MARS'
Plan hash value: 1689330390
---------------------------------------------------------
| Id | Operation                  | Name      | E-Rows  |
---------------------------------------------------------
0 | UPDATE STATEMENT           |           |         |
1 |  UPDATE                    | BIG_ZIGGY |         |
2 |   TABLE ACCESS STORAGE FULL| BIG_ZIGGY |    100K |
---------------------------------------------------------
Note
-----
- automatic DOP: Computed Degree of Parallelism is 1
- PDML disabled because object is not decorated with parallel clause
- Warning: basic plan statistics not available. These are only collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or system level
Statistics
-----------------------------------------------------------
345 CPU used by this session
347 CPU used when call started
399 DB time
3442830 RM usage
5 Requests to/from client
491 Session total flash IO requests
25403392 cell physical IO interconnect bytes
48814 consistent gets
10111 consistent gets examination
10111 consistent gets examination (fastpath)
48814 consistent gets from cache
38703 consistent gets pin
38702 consistent gets pin (fastpath)
544587 db block gets
544587 db block gets from cache
538582 db block gets from cache (fastpath)
127 enqueue releases
129 enqueue requests
3086 gcs affinity lock grants
803 gcs data block access records
3 ges messages sent
33574 global enqueue gets sync
33573 global enqueue releases
43 messages sent
483 non-idle wait count
44 non-idle wait time
8 opened cursors cumulative
1 opened cursors current
71 physical read requests optimized
420 physical read total IO requests
25403392 physical read total bytes
3219456 physical read total bytes optimized
1 pinned cursors current
4 process last non-idle time
55 recursive calls
1 recursive cpu usage
593401 session logical reads
42 user I/O wait time
6 user calls
Elapsed: 00:00:04.532
SQL> commit
Commit complete.

Note that the CPU used by session is 335, the number of db block gets is 544587 and that the raw elapsed time is 00:00:04.532. We’ll shortly compare these values with those of the same demo, but on a table with ENABLE ROW MOVEMENT set.

If we now check for migrated (chained) rows:

SQL> analyze table big_ziggy compute statistics;
Table BIG_ZIGGY analyzed.
SQL> select table_name, num_rows, chain_cnt from user_tables where table_name='BIG_ZIGGY';
TABLE_NAME     NUM_ROWS    CHAIN_CNT
_____________ ___________ ____________
BIG_ZIGGY          100000        28323

We notice we indeed have 28323 migrated rows.

We’ll now repeat the exactly same demo, but this time on the BIG_ZIGGY2 table that has ENABLE ROW MOVEMENT set:

SQL> CREATE TABLE big_ziggy2(id number, code1 number, code2 number, code3 number, code4 number, code5 number, code6 number, code7 number, code8 number, code9 number, code10 number, code11 number, code12 number, code13 number, code14 number, code15 number, code16 number, code17 number, code18 number, code19 number, code20 number, name varchar2(142)) PCTFREE 0 ENABLE ROW MOVEMENT;
Table BIG_ZIGGY2 created.
SQL> INSERT INTO big_ziggy2 SELECT rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, rownum, 'BOWIE' FROM dual CONNECT BY LEVEL <= 100000;
100,000 rows inserted.
SQL> commit;
Commit complete.
SQL> exec dbms_stats.gather_table_stats(ownname=> null, tabname=> 'BIG_ZIGGY2');
PL/SQL procedure successfully completed.
SQL> analyze table big_ziggy2 compute statistics;
Table BIG_ZIGGY2 analyzed.
SQL> select table_name, num_rows, chain_cnt from user_tables where table_name='BIG_ZIGGY2';
TABLE_NAME    NUM_ROWS    CHAIN_CNT
_____________ ___________ ____________
BIG_ZIGGY2         100000            0
SQL> create index big_ziggy2_id_i on big_ziggy2(id);
Index BIG_ZIGGY2_ID_I created.
SQL> create index big_ziggy2_code1_i on big_ziggy2(code1);
Index BIG_ZIGGY2_CODE1_I created.
SQL> create index big_ziggy2_code2_i on big_ziggy2(code2);
Index BIG_ZIGGY2_CODE2_I created.
SQL> create index big_ziggy2_code3_i on big_ziggy2(code3);
Index BIG_ZIGGY2_CODE3_I created.
SQL> create index big_ziggy2_code4_i on big_ziggy2(code4);
Index BIG_ZIGGY2_CODE4_I created.
SQL> create index big_ziggy2_code5_i on big_ziggy2(code5);
Index BIG_ZIGGY2_CODE5_I created.
SQL> create index big_ziggy2_code6_i on big_ziggy2(code6);
Index BIG_ZIGGY2_CODE6_I created.
SQL> create index big_ziggy2_code7_i on big_ziggy2(code7);
Index BIG_ZIGGY2_CODE7_I created.
SQL> create index big_ziggy2_code8_i on big_ziggy2(code8);
Index BIG_ZIGGY2_CODE8_I created.
SQL> create index big_ziggy2_code9_i on big_ziggy2(code9);
Index BIG_ZIGGY2_CODE9_I created.
SQL> create index big_ziggy2_code10_i on big_ziggy2(code10);
Index BIG_ZIGGY2_CODE10_I created.
SQL> create index big_ziggy2_code11_i on big_ziggy2(code11);
Index BIG_ZIGGY2_CODE11_I created.
SQL> create index big_ziggy2_code12_i on big_ziggy2(code12);
Index BIG_ZIGGY2_CODE12_I created.
SQL> create index big_ziggy2_code13_i on big_ziggy2(code13);
Index BIG_ZIGGY2_CODE13_I created.
SQL> create index big_ziggy2_code14_i on big_ziggy2(code14);
Index BIG_ZIGGY2_CODE14_I created.
SQL> create index big_ziggy2_code15_i on big_ziggy2(code15);
Index BIG_ZIGGY2_CODE15_I created.
SQL> create index big_ziggy2_code16_i on big_ziggy2(code16);
Index BIG_ZIGGY2_CODE16_I created.
SQL> create index big_ziggy2_code17_i on big_ziggy2(code17);
Index BIG_ZIGGY2_CODE17_I created.
SQL> create index big_ziggy2_code18_i on big_ziggy2(code18);
Index BIG_ZIGGY2_CODE18_I created.
SQL> create index big_ziggy2_code19_i on big_ziggy2(code19);
Index BIG_ZIGGY2_CODE19_I created.
SQL> create index big_ziggy2_code20_i on big_ziggy2(code20);
Index BIG_ZIGGY2_CODE20_I created.

If we now repeat the same UPDATE statement:

SQL> update big_ziggy2 set name='THE RISE AND FALL OF ZIGGY STARDUST AND THE SPIDERS FROM MARS';
100,000 rows updated.
PLAN_TABLE_OUTPUT
____________________________________________________________________________________
SQL_ID gupa6k30c341n, child number 0
-------------------------------------
update big_ziggy2 set name='THE RISE AND FALL OF ZIGGY STARDUST AND THE
SPIDERS FROM MARS'
Plan hash value: 3856369697
----------------------------------------------------------
| Id | Operation                  | Name       | E-Rows  |
----------------------------------------------------------
0 | UPDATE STATEMENT           |            |         |
1 |  UPDATE                    | BIG_ZIGGY2 |         |
2 |   TABLE ACCESS STORAGE FULL| BIG_ZIGGY2 |    100K |
----------------------------------------------------------
Note
-----
- automatic DOP: Computed Degree of Parallelism is 1
- PDML disabled because object is not decorated with parallel clause
- Warning: basic plan statistics not available. These are only collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or system level
Statistics
-----------------------------------------------------------
1310 CPU used by this session
1310 CPU used when call started
1732 DB time
13104856 RM usage
5 Requests to/from client
12 Session IORM flash wait time
13343 Session total flash IO requests
235888640 cell physical IO interconnect bytes
36437 consistent gets
994 consistent gets examination
994 consistent gets examination (fastpath)
36437 consistent gets from cache
35443 consistent gets pin
35275 consistent gets pin (fastpath)
2574278 db block gets
2574278 db block gets from cache
1418826 db block gets from cache (fastpath)
5729 enqueue releases
5731 enqueue requests
23745 gcs affinity lock grants
11119 gcs data block access records
25 ges messages sent
1165 global enqueue gets sync
1164 global enqueue releases
215 messages sent
8254 non-idle wait count
476 non-idle wait time
31 opened cursors cumulative
5324 physical read requests optimized
8019 physical read total IO requests
235888640 physical read total bytes
63856640 physical read total bytes optimized
17 process last non-idle time
160 recursive calls
7 recursive cpu usage
2610715 session logical reads
475 user I/O wait time
6 user calls
Elapsed: 00:00:17.600
SQL> commit
Commit complete.

We notice that this update consumed more resources than the previous example.

Note that the CPU used by session is now 1310 (previously 335), the number of db block gets is now 2574278 (previously 544587) and that the raw elapsed time has increased to 00:00:17.600 (previously it was 00:00:04.532).

SQLcl doesn’t automatically display redo statistics which is a shame and something I’ve only just noticed, but it will have increased significantly as I discussed previously.

However, if we look at the number of migrated rows on the BIG_ZIGGY2 table:

SQL> analyze table big_ziggy2 compute statistics;
Table BIG_ZIGGY2 analyzed.
SQL> select table_name, num_rows, chain_cnt from user_tables where table_name='BIG_ZIGGY2';
TABLE_NAME    NUM_ROWS    CHAIN_CNT
_____________ ___________ ____________
BIG_ZIGGY2         100000            0

We notice there are no rows considered chained (migrated), as in this scenario on Oracle Autonomous Databases, all rows that moved to a different block had their associated ROWIDs updated on the fly in all the corresponding indexes and as such there was no need to have the pointer in the original block to denote the row’s new location.

So the choice is entirely yours.

If you have applications that rely on stored ROWIDs not changing in the background when a row happens to migrate OR you have applications in which the performance of the UPDATE DML is absolutely paramount and you wish to avoid the overheads associated with updating ROWIDs on the fly (which in an Exadata environment is less likely to be an issue), then do NOT set ENABLE ROW MOVEMENT on the table.

Generally, the improvements associated with more efficient indexed-based accesses overrides the overheads associated with (usually) one-off and uncommon row migrations (which might be mitigated with more appropriate settings of PCTFREE).

That said, I’ll discuss a few other areas of potential concern associated with this change of behaviour in my next post…


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK