Thursday, September 30, 2021

Fake Values

Imagine you have accumulated five years worth of data into your accounting system, say from 01-jan-2000 to 31-Dec-2004 and decided to run a report spanning all data in 2003 it seems likely that all the queries that have the predicate where date_col between 01-jan-2003 to 31-Dec-2003 should be using tablescans since they are likely to be querying nearly 20% of data.
 
So lets create a table with approximately 100 rows per day for five years, but every thousand row is set to NULL and verify the optimizer estimates.
 
demo@XEPDB1> create table t as
  2  select decode( mod(rownum-1,1000),
  3     0, to_date(null),
  4     to_date('01-jan-2000','dd-mon-yyyy')+ trunc((rownum-1)/100) ) dt
  5  from dual
  6  connect by level <= ( 1827*100 );
 
Table created.
 
demo@XEPDB1> select num_rows,blocks
  2  from user_tab_statistics
  3  where table_name ='T';
 
  NUM_ROWS     BLOCKS
---------- ----------
    182700        343
 
demo@XEPDB1> select num_distinct,num_nulls,num_buckets,sample_size
  2  from user_tab_col_statistics
  3  where table_name ='T';
 
NUM_DISTINCT  NUM_NULLS NUM_BUCKETS SAMPLE_SIZE
------------ ---------- ----------- -----------
        1827        183           1      182517
 
demo@XEPDB1> select min(dt), max(dt) from t;
 
MIN(DT)     MAX(DT)
----------- -----------
01-JAN-2000 31-DEC-2004
 
So got 182700 rows in the table of which 183 rows are having null values. Given a query like below with a simple range based predicates
 
select * from t
where dt between to_date('01-jan-2003','dd-mon-yyyy')
and to_date('31-dec-2003','dd-mon-yyyy');
 
Here is what the optimizer internally doing a quick calculation for that range scan selectivity for the query
 
Selectivity = required range – available range + N / NDV.
Selectivity = ( 31-dec-2003 – 01-Jan-2003 ) / ( 31-dec-2004 – 01-jan-2000 ) + 2/ 1827 = .200437517
 
Now the cardinality is num_rows * selectivity =   (182700 – 183) * .200437517 = 36583.2542
 
demo@XEPDB1> set autotrace on exp
demo@XEPDB1> select count(*) from t
  2  where dt between to_date('01-jan-2003','dd-mon-yyyy')
  3  and to_date('31-dec-2003','dd-mon-yyyy');
 
  COUNT(*)
----------
     36463
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2966233522
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     8 |    98   (4)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |     8 |            |          |
|*  2 |   TABLE ACCESS FULL| T    | 36583 |   285K|    98   (4)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter("DT">=TO_DATE(' 2003-01-01 00:00:00', 'syyyy-mm-dd
              hh24:mi:ss') AND "DT"<=TO_DATE(' 2003-12-31 00:00:00', 'syyyy-mm-dd
              hh24:mi:ss'))
 
The estimates reported in the explain plan is close to the actuals, so what could possibly go wrong in real Oracle date columns?
 
As with any database independent applications, if we decide to use some future values (like 31-dec-4000) instead of nulls, will affect the optimizer estimes heavily.
 
Let’s say we update all NULL values in the above example to some future values, say 31-dec-4000 (or we decided to have some fake values like 31-dec-4000 instead of nulls )
 
demo@XEPDB1> update t set
  2  dt = to_date('31-dec-4000','dd-mon-yyyy')
  3  where dt is null ;
 
183 rows updated.
 
demo@XEPDB1> begin
  2     dbms_stats.gather_table_stats(
  3             user,'T',no_invalidate=>false,
  4             method_opt=>'for all columns size 1');
  5  end;
  6  /
 
PL/SQL procedure successfully completed.
 
demo@XEPDB1> select min(dt), max(dt) from t;
 
MIN(DT)     MAX(DT)
----------- -----------
01-JAN-2000 31-DEC-4000
 
Then the data distribution changes like this
 
demo@XEPDB1> select num_distinct,num_nulls,num_buckets,sample_size
  2  from user_tab_col_statistics
  3  where table_name ='T';
 
NUM_DISTINCT  NUM_NULLS NUM_BUCKETS SAMPLE_SIZE
------------ ---------- ----------- -----------
        1828          0           1      182700
 
demo@XEPDB1>
 
re-executing the same query with simple range based predicates on this new data distribution and the  calculation for the range scan selectivity is
 
Selectivity = required range – available range + N / NDV.
Selectivity = ( 31-dec-2003 – 01-Jan-2003 ) / ( 31-dec-4000 – 01-jan-2000 ) + 2/ 1828 = 0.001592
 
Now the cardinality is num_rows * selectivity =   (182700) * 0.001592 = 290.85
 
demo@XEPDB1> set autotrace on exp
demo@XEPDB1> select count(*) from t
  2  where dt between to_date('01-jan-2003','dd-mon-yyyy')
  3  and to_date('31-dec-2003','dd-mon-yyyy');
 
  COUNT(*)
----------
     36463
 
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2966233522
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     8 |    98   (4)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |     8 |            |          |
|*  2 |   TABLE ACCESS FULL| T    |   291 |  2328 |    98   (4)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter("DT"<=TO_DATE(' 2003-12-31 00:00:00', 'syyyy-mm-dd
              hh24:mi:ss') AND "DT">=TO_DATE(' 2003-01-01 00:00:00', 'syyyy-mm-dd
              hh24:mi:ss'))
 
With this misleading value for the selectivity (which translates into a dramatically incorrect cardinality), It is not surprising if the optimizer manages to pick the wrong path through the data, and it takes just one row with this unfortunate default value to make the statistics look like rubbish.


Monday, September 20, 2021

EZconnect Plus and Wallet

EZConnect is simple when we know the host and port. However, wallets are great mechanism for establishing password less connections to servers. However, the combination of these two were not easy before Oracle 19c.

 

We can add a wallet entry for an EZCONNECT connection string like ‘//localhost/pdb1’ but in the wallet, you would need a different connecting string for each user because it associates a user and password to a service name. and have multiple users connecting to a service.

 

demo@PDB19> show con_name

 

CON_NAME

------------------------------

PDB19

demo@PDB19> grant create session to app_user identified by app_user;

 

Grant succeeded.

 

demo@PDB19>

 

I created a wallet,

 

[oracle@en701c104vm01 ~]$ mkdir -p /home/oracle/wallet

[oracle@en701c104vm01 ~]$ mkstore -wrl /home/oracle/wallet -create

Oracle Secret Store Tool Release 23.0.0.0.0 - Production

Version 23.0.0.0.0

Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.

 

Enter password:

Enter password again:

 

Added an entry for the service “app_user_demo” connecting to PDB19 with the service account name as “app_user”

 

[oracle@en701c104vm01 ~]$ mkstore -wrl /home/oracle/wallet -createCredential app_user_demo app_user

Oracle Secret Store Tool Release 23.0.0.0.0 - Production

Version 23.0.0.0.0

Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.

 

Your secret/Password is missing in the command line

Enter your secret/Password:

Re-enter your secret/Password:

Enter wallet password:

[oracle@en701c104vm01 ~]$

 

“Then defined the sqlnet.ora with the wallet_location parameter and the tnsnames.ora for this “app_user_demo” entry,

 

[oracle@en701c104vm01 ~]$ cat $ORACLE_HOME/network/admin/sqlnet.ora

SQLNET.ENCRYPTION_SERVER = required

 

SQLNET.CRYPTO_CHECKSUM_SERVER = required

 

SQLNET.CRYPTO_CHECKSUM_TYPES_SERVER = (SHA1)

 

ENCRYPTION_WALLET_LOCATION =

 (SOURCE=

  (METHOD=FILE)

   (METHOD_DATA=

    (DIRECTORY=/var/opt/oracle/dbaas_acfs/XEN701C1/tde_wallet)))

 

SQLNET.ENCRYPTION_TYPES_SERVER = (AES256, AES192, AES128)

 

SQLNET.EXPIRE_TIME = 10

 

SQLNET.WALLET_OVERRIDE = FALSE

 

WALLET_LOCATION = (SOURCE=(METHOD=FILE)(METHOD_DATA=(DIRECTORY=/home/oracle/wallet)))

SQLNET.WALLET_OVERRIDE=TRUE

 

SSL_VERSION = 1.2

 

[oracle@en701c104vm01 ~]$ tnsping app_user_demo

 

TNS Ping Utility for Linux: Version 19.0.0.0.0 - Production on 17-SEP-2021 10:16:10

 

Copyright (c) 1997, 2021, Oracle.  All rights reserved.

 

Used parameter files:

/u02/app/oracle/product/19.0.0.0/dbhome_2/network/admin/XEN701C1/sqlnet.ora

 

Used TNSNAMES adapter to resolve the alias

Attempting to contact (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = PDB19) (FAILOVER_MODE = (TYPE = select) (METHOD = basic))))

OK (0 msec)

 

Now we can connect password less when using sqlplus

 

[oracle@en701c104vm01 ~]$ sqlplus /@app_user_demo

 

SQL*Plus: Release 19.0.0.0.0 - Production on Fri Sep 17 10:26:55 2021

Version 19.12.0.0.0

 

Copyright (c) 1982, 2021, Oracle.  All rights reserved.

 

Last Successful login time: Fri Sep 17 2021 10:25:29 -04:00

 

Connected to:

Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production

Version 19.12.0.0.0

 

SQL> show user

USER is "APP_USER"

SQL> show con_name

 

CON_NAME

------------------------------

PDB19

SQL>

 

Now for easy connect we can add a new entry for EZCONNECT string

 

[oracle@en701c104vm01 ~]$ mkstore -wrl /home/oracle/wallet -createCredential //10.41.41.8/PDB19 app_user

Oracle Secret Store Tool Release 23.0.0.0.0 - Production

Version 23.0.0.0.0

Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.

 

Your secret/Password is missing in the command line

Enter your secret/Password:

Re-enter your secret/Password:

Enter wallet password:

 

We can now connect using EZCONNECT wallet entry like this

 

[oracle@en701c104vm01 ~]$ sqlplus /@//10.41.41.8/PDB19

 

SQL*Plus: Release 19.0.0.0.0 - Production on Fri Sep 17 10:41:00 2021

Version 19.12.0.0.0

 

Copyright (c) 1982, 2021, Oracle.  All rights reserved.

 

Last Successful login time: Fri Sep 17 2021 10:26:57 -04:00

 

Connected to:

Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production

Version 19.12.0.0.0

 

SQL> show user

USER is "APP_USER"

SQL> show con_name

 

CON_NAME

------------------------------

PDB19

SQL> exit

Disconnected from Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production

Version 19.12.0.0.0

 

But what you do when you need to connect with different users? With a tnsnames.ora we can have multiple entries for each one like this

 

scott_demo,app_user_demo =

  (DESCRIPTION =

    (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))

    (CONNECT_DATA =

      (SERVER = DEDICATED)

      (SERVICE_NAME = PDB19)

      (FAILOVER_MODE =

        (TYPE = select)

        (METHOD = basic)

      )

    )

  )

 

And then define a credential for each one, but that is not possible with EZCONNECT, associating two different (service user) accounts to same EZCONNECT endpoints will return error like this

 

[oracle@en701c104vm01 ~]$ mkstore -wrl /home/oracle/wallet -createCredential //10.41.41.8/PDB19 app_user

Oracle Secret Store Tool Release 23.0.0.0.0 - Production

Version 23.0.0.0.0

Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.

 

Your secret/Password is missing in the command line

Enter your secret/Password:

Re-enter your secret/Password:

Enter wallet password:

[oracle@en701c104vm01 ~]$ mkstore -wrl /home/oracle/wallet -createCredential //10.41.41.8/PDB19 scott

Oracle Secret Store Tool Release 23.0.0.0.0 - Production

Version 23.0.0.0.0

Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.

 

Your secret/Password is missing in the command line

Enter your secret/Password:

Re-enter your secret/Password:

Enter wallet password:

Secret Store error occurred: oracle.security.pki.OracleSecretStoreException: Credential already exists

[oracle@en701c104vm01 ~]$

 

Oracle 19c extends the EZCONNECT syntax – namely EZCONNECT PLUS – with that it is still possible to add dummy parameters and using this option we can resolve the above error (differentiate multiple entries connecting to same database but with different user accounts)

 

Here is an example of it.

 

[oracle@en701c104vm01 ~]$ mkstore -wrl /home/oracle/wallet -createCredential //10.41.41.8/PDB19?My_Tag=app_user app_user

Oracle Secret Store Tool Release 23.0.0.0.0 - Production

Version 23.0.0.0.0

Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.

 

Your secret/Password is missing in the command line

Enter your secret/Password:

Re-enter your secret/Password:

Enter wallet password:

oracle@en701c104vm01 ~]$

 

This just add a parameter that will be ignored, but still helps to differentiate multiple entries.

 

[oracle@en701c104vm01 ~]$ tnsping //10.41.41.8/PDB19?My_Tag=app_user

 

TNS Ping Utility for Linux: Version 19.0.0.0.0 - Production on 17-SEP-2021 10:48:45

 

Copyright (c) 1997, 2021, Oracle.  All rights reserved.

 

Used parameter files:

/u02/app/oracle/product/19.0.0.0/dbhome_2/network/admin/XEN701C1/sqlnet.ora

 

Used HOSTNAME adapter to resolve the alias

Attempting to contact (DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=PDB19))(My_Tag=app_user)(ADDRESS=(PROTOCOL=tcp)(HOST=10.41.41.8)(PORT=1521)))

OK (0 msec)

 

Here is the connection the app_user service account using the credentials from Wallet.

 

[oracle@en701c104vm01 ~]$ sqlplus /@//10.41.41.8/PDB19?My_Tag=app_user

 

SQL*Plus: Release 19.0.0.0.0 - Production on Fri Sep 17 10:49:02 2021

Version 19.12.0.0.0

 

Copyright (c) 1982, 2021, Oracle.  All rights reserved.

 

Last Successful login time: Fri Sep 17 2021 10:41:01 -04:00

 

Connected to:

Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production

Version 19.12.0.0.0

 

SQL> show user

USER is "APP_USER"

SQL> show con_name

 

CON_NAME

------------------------------

PDB19

 

Similarly, for the scott user account we do something like this

 

[oracle@en701c104vm01 ~]$ mkstore -wrl /home/oracle/wallet -createCredential //10.41.41.8/PDB19?My_Tag=scott scott

Oracle Secret Store Tool Release 23.0.0.0.0 - Production

Version 23.0.0.0.0

Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.

 

Your secret/Password is missing in the command line

Enter your secret/Password:

Re-enter your secret/Password:

Enter wallet password:

[oracle@en701c104vm01 ~]$ tnsping //10.41.41.8/PDB19?My_Tag=scott

 

TNS Ping Utility for Linux: Version 19.0.0.0.0 - Production on 17-SEP-2021 10:50:21

 

Copyright (c) 1997, 2021, Oracle.  All rights reserved.

 

Used parameter files:

/u02/app/oracle/product/19.0.0.0/dbhome_2/network/admin/XEN701C1/sqlnet.ora

 

Used HOSTNAME adapter to resolve the alias

Attempting to contact (DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=PDB19))(My_Tag=scott)(ADDRESS=(PROTOCOL=tcp)(HOST=10.41.41.8)(PORT=1521)))

OK (0 msec)

[oracle@en701c104vm01 ~]$ sqlplus /@//10.41.41.8/PDB19?My_Tag=scott

 

SQL*Plus: Release 19.0.0.0.0 - Production on Fri Sep 17 10:50:44 2021

Version 19.12.0.0.0

 

Copyright (c) 1982, 2021, Oracle.  All rights reserved.

 

Last Successful login time: Fri Sep 17 2021 10:46:50 -04:00

 

Connected to:

Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production

Version 19.12.0.0.0

 

SQL> show user

USER is "SCOTT"

SQL> show con_name

 

CON_NAME

------------------------------

PDB19

SQL> exit

Disconnected from Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production

Version 19.12.0.0.0

[oracle@en701c104vm01 ~]$

 

So using EZCONNECTPLUS all we need is a sqlnet.ora and a wallet, but no tnsnames.ora

 

Here is all the entries I have

 

[oracle@en701c104vm01 ~]$ mkstore -wrl /home/oracle/wallet -listCredential

Oracle Secret Store Tool Release 23.0.0.0.0 - Production

Version 23.0.0.0.0

Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.

 

Enter wallet password:

List credential (index: connect_string username)

5: //10.41.41.8/PDB19?My_Tag=scott scott

4: //10.41.41.8/PDB19?My_Tag=app_user app_user

3: scott_demo scott

2: //10.41.41.8/PDB19 app_user

1: app_user_demo app_user

[oracle@en701c104vm01 ~]$

Thursday, September 16, 2021

Gradual Database Password Rollover

Oracle database 21c introduced a new feature called, Gradual database password rollover – the ability to allow service user accounts passwords updated online completely, without downtime – with the latest RU this got back ported to 19c (long term release version 19.12)

 

With this new feature, password of an application user account can be changed without having to schedule a downtime. Which is really a great & this can be done using a new profile parameter PASSWORD_ROLLOVER_TIME, this will set the rollover period of time (a small window period) where the application can still log in using either the old and new passwords, administrator does not need any more to take the application down when the application password is changed.

 

To see how this works, let’s create a profile

 

demo@PDB19> create profile password_rollover_test

  2  limit

  3  password_rollover_time 1/24;

 

Profile created.

 

Note the parameter PASSWORD_ROLLOVER_TIME is set of 1/24 which means 1 hour (the least value that this parameter can have and at max can go with 60 days or the lower value of PASSWORD_LIFE_TIME or PASSWORD_GRACE_TIME or whichever is lower).

 

Let’s create a new user and by default associated with “DEFAULT” profile.

 

demo@PDB19> grant create session to app_user1

  2  identified by "Password-1";

 

Grant succeeded.

 

demo@PDB19> conn app_user1/Password-1@pdb19

Connected.

app_user1@PDB19> conn demo/demo@pdb19

Connected.

demo@PDB19> select account_status,profile

  2  from dba_users

  3  where username ='APP_USER1';

 

ACCOUNT_STATUS                   PROFILE

-------------------------------- --------------------

OPEN                             DEFAULT

 

Now lets assign this new user with the profile created above and change the password for the service account created to get started with Password rollover period feature.

 

demo@PDB19> alter user app_user1 profile password_rollover_test;

 

User altered.

 

demo@PDB19> alter user app_user1 identified by "app_user1";

 

User altered.

 

During this rollover period window, we can connect to this service account using either new or old passwords.

demo@PDB19> select account_status,profile

  2  from dba_users

  3  where username ='APP_USER1';

 

ACCOUNT_STATUS                   PROFILE

-------------------------------- -------------------------

OPEN & IN ROLLOVER               PASSWORD_ROLLOVER_TEST

 

demo@PDB19> conn app_user1/Password-1@pdb19

Connected.

app_user1@PDB19> conn app_user1/app_user1@pdb19

Connected.

app_user1@PDB19>

 

We can see that the connection is successfully done with both passwords during the rollover period. To end the rollover period

·         Let the password rollover expire on its own.

·         As either the user or administrator we can run the explicit expire password rollover period

 

demo@PDB19> alter user app_user1 expire password rollover period;

 

User altered.

 

During the rollover period, the unified auditing will keep track of user authentication using old passwords

 

demo@PDB19> select os_username,userhost,terminal,event_timestamp,authentication_type

  2  from unified_audit_trail

  3  where action_name ='LOGON'

  4  and authentication_type like '%VERIFIER%OLD%';

 

OS_USERNAM USERHOST                  TERMINAL        EVENT_TIMESTAMP                AUTHENTICATION_TYPE

---------- ------------------------- --------------- ------------------------------ ----------------------------------------

v-rjeyabal DOMAIN\COZYWVDIXD1517     COZYWVDIXD1517  16-SEP-21 08.19.18.772835 PM   (TYPE=(DATABASE));(CLIENT ADDRESS=((ADDR

                                                                                    ESS=(PROTOCOL=tcp)(HOST=10.69.73.120)(PO

                                                                                    RT=56398))));(CLIENT ADDRESS=());(LOGON_

                                                                                    INFO=((VERIFIER=12C-OLD)(CLIENT_CAPABILI

                                                                                    TIES=O5L_NP,O7L_MR,O8L_LI)));

 

Once the rollover period was completed, authentication using old password is not possible.

 

demo@PDB19> conn app_user1/Password-1@pdb19

ERROR:

ORA-01017: invalid username/password; logon denied

 

 

Warning: You are no longer connected to ORACLE.

demo@PDB19> conn app_user1/app_user1@pdb19

Connected.

app_user1@PDB19> conn demo/demo@pdb19

Connected.

demo@PDB19> select account_status,profile

  2  from dba_users

  3  where username ='APP_USER1';

 

ACCOUNT_STATUS                   PROFILE

-------------------------------- -----------------------

OPEN                             PASSWORD_ROLLOVER_TEST

 

demo@PDB19>