Some Things To Consider Now ROWIDs Are Updated When Rows Migrate Part I (“More”)
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.
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
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, code 1 number, code 2 number, code 3 number, code 4 number, code 5 number, code 6 number, code 7 number, code 8 number, code 9 number, code 10 number, code 11 number, code 12 number, code 13 number, code 14 number, code 15 number, code 16 number, code 17 number, code 18 number, code 19 number, code 20 number, name varchar 2 ( 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_code 1 _i on big_ziggy(code 1 ); Index BIG_ZIGGY_CODE 1 _I created. SQL> create index big_ziggy_code 2 _i on big_ziggy(code 2 ); Index BIG_ZIGGY_CODE 2 _I created. SQL> create index big_ziggy_code 3 _i on big_ziggy(code 3 ); Index BIG_ZIGGY_CODE 3 _I created. SQL> create index big_ziggy_code 4 _i on big_ziggy(code 4 ); Index BIG_ZIGGY_CODE 4 _I created. SQL> create index big_ziggy_code 5 _i on big_ziggy(code 5 ); Index BIG_ZIGGY_CODE 5 _I created. SQL> create index big_ziggy_code 6 _i on big_ziggy(code 6 ); Index BIG_ZIGGY_CODE 6 _I created. SQL> create index big_ziggy_code 7 _i on big_ziggy(code 7 ); Index BIG_ZIGGY_CODE 7 _I created. SQL> create index big_ziggy_code 8 _i on big_ziggy(code 8 ); Index BIG_ZIGGY_CODE 8 _I created. SQL> create index big_ziggy_code 9 _i on big_ziggy(code 9 ); Index BIG_ZIGGY_CODE 9 _I created. SQL> create index big_ziggy_code 10 _i on big_ziggy(code 10 ); Index BIG_ZIGGY_CODE 10 _I created. SQL> create index big_ziggy_code 11 _i on big_ziggy(code 11 ); Index BIG_ZIGGY_CODE 11 _I created. SQL> create index big_ziggy_code 12 _i on big_ziggy(code 12 ); Index BIG_ZIGGY_CODE 12 _I created. SQL> create index big_ziggy_code 13 _i on big_ziggy(code 13 ); Index BIG_ZIGGY_CODE 13 _I created. SQL> create index big_ziggy_code 14 _i on big_ziggy(code 14 ); Index BIG_ZIGGY_CODE 14 _I created. SQL> create index big_ziggy_code 15 _i on big_ziggy(code 15 ); Index BIG_ZIGGY_CODE 15 _I created. SQL> create index big_ziggy_code 16 _i on big_ziggy(code 16 ); Index BIG_ZIGGY_CODE 16 _I created. SQL> create index big_ziggy_code 17 _i on big_ziggy(code 17 ); Index BIG_ZIGGY_CODE 17 _I created. SQL> create index big_ziggy_code 18 _i on big_ziggy(code 18 ); Index BIG_ZIGGY_CODE 18 _I created. SQL> create index big_ziggy_code 19 _i on big_ziggy(code 19 ); Index BIG_ZIGGY_CODE 19 _I created. SQL> create index big_ziggy_code 20 _i on big_ziggy(code 20 ); Index BIG_ZIGGY_CODE 20 _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 53 xtnn 8 mmtwj 5 , 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 | 100 K | --------------------------------------------------------- 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_ziggy 2 (id number, code 1 number, code 2 number, code 3 number, code 4 number, code 5 number, code 6 number, code 7 number, code 8 number, code 9 number, code 10 number, code 11 number, code 12 number, code 13 number, code 14 number, code 15 number, code 16 number, code 17 number, code 18 number, code 19 number, code 20 number, name varchar 2 ( 142 )) PCTFREE 0 ENABLE ROW MOVEMENT; Table BIG_ZIGGY 2 created. SQL> INSERT INTO big_ziggy 2 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_ziggy 2 compute statistics; Table BIG_ZIGGY 2 analyzed. SQL> select table_name, num_rows, chain_cnt from user_tables where table_name= 'BIG_ZIGGY2' ; TABLE_NAME NUM_ROWS CHAIN_CNT _____________ ___________ ____________ BIG_ZIGGY 2 100000 0 SQL> create index big_ziggy 2 _id_i on big_ziggy 2 (id); Index BIG_ZIGGY 2 _ID_I created. SQL> create index big_ziggy 2 _code 1 _i on big_ziggy 2 (code 1 ); Index BIG_ZIGGY 2 _CODE 1 _I created. SQL> create index big_ziggy 2 _code 2 _i on big_ziggy 2 (code 2 ); Index BIG_ZIGGY 2 _CODE 2 _I created. SQL> create index big_ziggy 2 _code 3 _i on big_ziggy 2 (code 3 ); Index BIG_ZIGGY 2 _CODE 3 _I created. SQL> create index big_ziggy 2 _code 4 _i on big_ziggy 2 (code 4 ); Index BIG_ZIGGY 2 _CODE 4 _I created. SQL> create index big_ziggy 2 _code 5 _i on big_ziggy 2 (code 5 ); Index BIG_ZIGGY 2 _CODE 5 _I created. SQL> create index big_ziggy 2 _code 6 _i on big_ziggy 2 (code 6 ); Index BIG_ZIGGY 2 _CODE 6 _I created. SQL> create index big_ziggy 2 _code 7 _i on big_ziggy 2 (code 7 ); Index BIG_ZIGGY 2 _CODE 7 _I created. SQL> create index big_ziggy 2 _code 8 _i on big_ziggy 2 (code 8 ); Index BIG_ZIGGY 2 _CODE 8 _I created. SQL> create index big_ziggy 2 _code 9 _i on big_ziggy 2 (code 9 ); Index BIG_ZIGGY 2 _CODE 9 _I created. SQL> create index big_ziggy 2 _code 10 _i on big_ziggy 2 (code 10 ); Index BIG_ZIGGY 2 _CODE 10 _I created. SQL> create index big_ziggy 2 _code 11 _i on big_ziggy 2 (code 11 ); Index BIG_ZIGGY 2 _CODE 11 _I created. SQL> create index big_ziggy 2 _code 12 _i on big_ziggy 2 (code 12 ); Index BIG_ZIGGY 2 _CODE 12 _I created. SQL> create index big_ziggy 2 _code 13 _i on big_ziggy 2 (code 13 ); Index BIG_ZIGGY 2 _CODE 13 _I created. SQL> create index big_ziggy 2 _code 14 _i on big_ziggy 2 (code 14 ); Index BIG_ZIGGY 2 _CODE 14 _I created. SQL> create index big_ziggy 2 _code 15 _i on big_ziggy 2 (code 15 ); Index BIG_ZIGGY 2 _CODE 15 _I created. SQL> create index big_ziggy 2 _code 16 _i on big_ziggy 2 (code 16 ); Index BIG_ZIGGY 2 _CODE 16 _I created. SQL> create index big_ziggy 2 _code 17 _i on big_ziggy 2 (code 17 ); Index BIG_ZIGGY 2 _CODE 17 _I created. SQL> create index big_ziggy 2 _code 18 _i on big_ziggy 2 (code 18 ); Index BIG_ZIGGY 2 _CODE 18 _I created. SQL> create index big_ziggy 2 _code 19 _i on big_ziggy 2 (code 19 ); Index BIG_ZIGGY 2 _CODE 19 _I created. SQL> create index big_ziggy 2 _code 20 _i on big_ziggy 2 (code 20 ); Index BIG_ZIGGY 2 _CODE 20 _I created. |
If we now repeat the same UPDATE statement:
SQL> update big_ziggy 2 set name= 'THE RISE AND FALL OF ZIGGY STARDUST AND THE SPIDERS FROM MARS' ; 100 , 000 rows updated. PLAN_TABLE_OUTPUT ____________________________________________________________________________________ SQL_ID gupa 6 k 30 c 341 n, child number 0 ------------------------------------- update big_ziggy 2 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_ZIGGY 2 | | | 2 | TABLE ACCESS STORAGE FULL| BIG_ZIGGY 2 | 100 K | ---------------------------------------------------------- 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_ziggy 2 compute statistics; Table BIG_ZIGGY 2 analyzed. SQL> select table_name, num_rows, chain_cnt from user_tables where table_name= 'BIG_ZIGGY2' ; TABLE_NAME NUM_ROWS CHAIN_CNT _____________ ___________ ____________ BIG_ZIGGY 2 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…
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK