Oracle Tips and Tricks — David Fitzjarrell

March 19, 2009

Hanging With The Locals

Filed under: General — dfitzjarrell @ 14:48

There are still databases in existence using dictionary-managed tablespaces, which means that the SYSTEM tablespaces in these databases are also dictionary-managed (which presents other issues besides the extent management, such as using the SYSTEM tablespace as a temporary tablespace and having a TEMP tablespace utilizing permanent files). Converting these tablespaces ‘in-place’ is possible with a packaged procedure provided by Oracle, but this method doesn’t work very well in terms of making the dictionary-managed tablespace a true locally managed one. Let’s look at the results of a conversion and see where this method falls short of the mark.

DBA_TABLESPACES contains information on the extent management, extent sizing, block_size, status and other various aspects of all of the tablespaces in a given database. The current description is:

SQL> desc dba_tablespaces
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TABLESPACE_NAME                           NOT NULL VARCHAR2(30)
 BLOCK_SIZE                                NOT NULL NUMBER
 INITIAL_EXTENT                                     NUMBER
 NEXT_EXTENT                                        NUMBER
 MIN_EXTENTS                               NOT NULL NUMBER
 MAX_EXTENTS                                        NUMBER
 PCT_INCREASE                                       NUMBER
 MIN_EXTLEN                                         NUMBER
 STATUS                                             VARCHAR2(9)
 CONTENTS                                           VARCHAR2(9)
 LOGGING                                            VARCHAR2(9)
 FORCE_LOGGING                                      VARCHAR2(3)
 EXTENT_MANAGEMENT                                  VARCHAR2(10)
 ALLOCATION_TYPE                                    VARCHAR2(9)
 PLUGGED_IN                                         VARCHAR2(3)
 SEGMENT_SPACE_MANAGEMENT                           VARCHAR2(6)
 DEF_TAB_COMPRESSION                                VARCHAR2(8)
 RETENTION                                          VARCHAR2(11)
 BIGFILE                                            VARCHAR2(3)
 
SQL>

Let’s look at some information about tablespaces created as locally managed:

TABLESPACE_NAME                INITIAL_EXTENT NEXT_EXTENT EXTENT_MAN ALLOCATIO MIN_EXTLEN
------------------------------ -------------- ----------- ---------- --------- ----------
UNDOTBS1                                65536             LOCAL      SYSTEM         65536
SYSAUX                                  65536             LOCAL      SYSTEM         65536

Notice that the ALLOCATION_TYPE is listed as SYSTEM, meaning these are created AUTOALLOCATE, with extents starting at 64K and systematically increasing in size. Notice also that the next extent is NULL, and that the minimum extent length is 64 K. Let’s now look at tablespaces which were dictionary-managed that were converted with the DBMS_SPACE_ADMIN.TABLESPACE_MIGRATE_TO_LOCAL procedure:

TABLESPACE_NAME                INITIAL_EXTENT NEXT_EXTENT EXTENT_MAN ALLOCATIO MIN_EXTLEN
------------------------------ -------------- ----------- ---------- --------- ----------
TOOLS                                   32768       32768 LOCAL      USER           32768
USERS                                  131072      131072 LOCAL      USER          131072

Here we see a different picture: the ALLOCATION_TYPE is still set to USER, the INITIAL_EXTENT and NEXT_EXTENT fields are populated, not necessarily with matching values and (although not shown here) the PCT_INCREASE values are retained (a true locally managed tablespace has a PCTINCREASE of NULL). Such omissions can create problems later, as free space in these ‘mongrel’ tablespaces may not coalesce, even by brute force methods and next extent sizing can grow since the PCTINCREASE is not ignored. The initial and next extents can also provide problems as they may not be multiples of 64K (as are the extent sizes of a locally managed, autoallocate tablespace) or they may not be uniform in size (due to the pctincrease). Remember, too, that any objects in these tablespaces won’t be rebuilt with the ‘standard’ extent sizes provided by true locally managed tablespaces, and as the free space coalescing may not be reliable in a converted tablespace free space fragmentation can be a serious issue. In true locally managed tablespaces free space fragmentation isn’t a problem because either the extents are all multiples of 64K or all of the extents are uniformly sized. In either case the freed extents can be reused by any new object which needs them. Not so with a converted tablespace, as extent sizes can vary and can be, well, ‘interesting’ sizes so that fragmentation can be an issue, especially when the coalesce functionality fails.

So, how best to convert dictionary-managed tablespaces to locally managed? The most reliable method is to create a new tablespace, locally managed, and move the objects from the old dictionary-managed tablespace to the new, locally managed one and then drop the old dictionary-managed tablespace. Of course you can’t do this if it’s the SYSTEM tablespace; you’re then stuck using the DBMS_SPACE_ADMIN package to migrate this tablespace, and all other dictionary-managed tablespace, in place (if that’s possible as there are restrictions for using DBMS_SPACE_ADMIN.TABLESPACE_MIGRATE_TO_LOCAL). No dictionary-managed tablespaces can exist in read/write mode in a database where the SYSTEM tablespace is locally managed, so any and all dictionary-managed tablespaces other than SYSTEM need to be migrated BEFORE the SYSTEM tablespace is migrated. The only other way to do this is to create a new database with a locally managed SYSTEM tablespace and, using a full export, relocate the objects from the source database.

So hanging with the locals is possible, but taking shortcuts usually isn’t the best way to get the job done because you may find yourself in worse shape than if you did nothing at all.

March 11, 2009

How Much Wood Could A Woodchuck Chuck …

Filed under: General — dfitzjarrell @ 14:46

Some of the most interesting questions pass through http://www.google.com, like this one:

“how to know how much table space is allocated for a user in oracle”

How DO we know how much space a given user account can consume in a tablespace? Two views can produce that report, DBA_TS_QUOTAS and DBA_DATA_FILES. Let’s see how that can be done.

DBA_TS_QUOTAS provides information on which tablespaces a given user can use and how much space they can consume:

SQL> desc dba_ts_quotas
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 TABLESPACE_NAME                           NOT NULL VARCHAR2(30)
 USERNAME                                  NOT NULL VARCHAR2(30)
 BYTES                                              NUMBER
 MAX_BYTES                                          NUMBER
 BLOCKS                                             NUMBER
 MAX_BLOCKS                                         NUMBER
 DROPPED                                            VARCHAR2(3) (available in 11g)

SQL>

This view lists the tablespaces for which a user has an assigned quota, so not all of the tablespaces in the database will be listed for non-DBA users. MAX_BYTES reports the actual size of the granted quota and if that quota is UNLIMITED the value of MAX_BYTES is -1. Likewise for MAX_BLOCKS, which translates the MAX_BYTES column into blocks based upon the db_block_size parameter set at database creation.

Onward and upward to DBA_DATA_FILES:

SQL> desc dba_data_files
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 FILE_NAME                                          VARCHAR2(513)
 FILE_ID                                            NUMBER
 TABLESPACE_NAME                                    VARCHAR2(30)
 BYTES                                              NUMBER
 BLOCKS                                             NUMBER
 STATUS                                             VARCHAR2(9)
 RELATIVE_FNO                                       NUMBER
 AUTOEXTENSIBLE                                     VARCHAR2(3)
 MAXBYTES                                           NUMBER
 MAXBLOCKS                                          NUMBER
 INCREMENT_BY                                       NUMBER
 USER_BYTES                                         NUMBER
 USER_BLOCKS                                        NUMBER
 ONLINE_STATUS                                      VARCHAR2(7)

SQL>

The columns of interest in this view are TABLESPACE_NAME, BYTES and BLOCKS since we’ll sum the last two columns, by tablespace, to obtain the total configured space available.

Now it’s time to put this all together and return our report. We’ll use subquery factoring to assist in this query (available in 9i and later releases of Oracle):

SQL> with ttlbytes as (
  2     select tablespace_name, sum(bytes) ttl_bytes, sum(blocks) ttl_blocks
  3     from dba_data_files
  4     group by tablespace_name
  5  ),
  6  userquotas as(
  7     select
  8     TABLESPACE_NAME,
  9     USERNAME,
 10     BYTES,
 11     max_bytes,
 12     blocks,
 13     MAX_BLOCKS
 14     from dba_ts_quotas
 15     where username = upper('&&1')
 16  )
 17  select
 18  s.tablespace_name,
 19  nvl(q.username, upper('&&1')) username,
 20  nvl(q.bytes,0) bytes,
 21  case when q.MAX_BYTES = -1 then s.ttl_bytes else nvl(q.max_bytes,0) end max_bytes,
 22  nvl(q.BLOCKS,0) blocks,
 23  case when q.MAX_BLOCKS = -1 then s.ttl_blocks else nvl(q.max_blocks,0) end max_blocks
 24  from userquotas q full outer join ttlbytes s
 25        on (q.tablespace_name = s.tablespace_name)
 26  order by q.username, s.tablespace_name;

TABLESPACE_NAME                USERNAME        BYTES   MAX_BYTES     BLOCKS  MAX_BLOCKS
------------------------------ ---------- ---------- ----------- ---------- -----------
TOOLS                          BING                0    10485760          0        1280  
USERS                          BING         17235968  2899247104       2104      353912
EXAMPLE                        BING                0           0          0           0
STATDATA                       BING                0           0          0           0
SYSAUX                         BING                0           0          0           0
SYSTEM                         BING                0           0          0           0
UNDOTBS1                       BING                0           0          0           0

7 rows selected.

SQL>

Notice the report returns all of the tablespaces in the database, with user information for the tablespaces the given user can utilize.

Can this be done for a database release earlier than 9i? You bet (but, really, you should upgrade to a supported release of Oracle):

SQL> select
  2  s.tablespace_name,
  3  nvl(q.username, upper('&&1')) username,
  4  nvl(q.bytes,0) bytes,
  5  decode(q.MAX_BYTES, -1, s.ttl_bytes, nvl(q.max_bytes,0)) max_bytes,
  6  nvl(q.BLOCKS,0) blocks,
  7  decode(q.MAX_BLOCKS, -1, s.ttl_blocks, nvl(q.max_blocks,0)) max_blocks
  8  from (
  9     select
 10     TABLESPACE_NAME,
 11     USERNAME,
 12     BYTES,
 13          max_bytes,
 14     blocks,
 15     MAX_BLOCKS
 16     from dba_ts_quotas
 17     where username = upper('&&1')
 18  ) q, (
 19     select tablespace_name, sum(bytes) ttl_bytes, sum(blocks) ttl_blocks
 20     from dba_data_files
 21     group by tablespace_name
 22  ) s
 23  where q.tablespace_name (+)= s.tablespace_name
 24  order by q.username, s.tablespace_name;

TABLESPACE_NAME                USERNAME           BYTES   MAX_BYTES     BLOCKS  MAX_BLOCKS
------------------------------ ------------- ---------- ----------- ---------- -----------
TOOLS                          BING                   0    10485760          0        1280
USERS                          BING            17235968  2899247104       2104      353912
EXAMPLE                        BING                   0           0          0           0
STATDATA                       BING                   0           0          0           0
SYSAUX                         BING                   0           0          0           0
SYSTEM                         BING                   0           0          0           0
UNDOTBS1                       BING                   0           0          0           0

7 rows selected.

SQL>

So answering the question listed at the start of this post is fairly easy and straightforward. And you get even more information than originally requested since you also see the tablespaces for which a given user has no quota assigned. Pretty slick if you ask me.

This report query can be modified to also flag when a user is close to reaching the limit on space in a tablespace. The 9i and later version becomes:

SQL> SQL> with ttlbytes as (
  2     select tablespace_name, sum(bytes) ttl_bytes, sum(blocks) ttl_blocks
  3     from dba_data_files
  4     group by tablespace_name
  5  ),
  6  userquotas as(
  7     select
  8     TABLESPACE_NAME,
  9     USERNAME,
 10     bytes,
 11     nvl(max_bytes,0) max_bytes,
 12     nvl(blocks,0) blocks,
 13     nvl(max_blocks,0) MAX_BLOCKS
 14     from dba_ts_quotas
 15     where username = upper('&&1')
 16  )
 17  select
 18  s.tablespace_name,
 19  nvl(q.username, upper('&&1')) username,
 20  nvl(BYTES,0) bytes,
 21  case when nvl(q.MAX_BYTES,0) = -1 then s.ttl_bytes else nvl(q.max_bytes,0) end max_bytes,
 22  nvl(blocks, 0) blocks,
 23  case when nvl(q.MAX_BLOCKS,0) = -1 then s.ttl_blocks else nvl(q.max_blocks,0) end max_blocks,
 24  case when nvl(q.bytes,0) > 0 and abs(nvl(q.max_bytes,0) - nvl(q.bytes,0))  0 and abs(nvl(q.max_bytes,0) - nvl(q.bytes,0)) between 102400 and 10240000 then 'WARNING'
 26       else 'OK' end status
 27  from userquotas q full outer join ttlbytes s
 28        on (q.tablespace_name = s.tablespace_name)
 29  order by q.username, s.tablespace_name;

TABLESPACE_NAME                USERNAME                            BYTES  MAX_BYTES     BLOCKS MAX_BLOCKS STATUS
------------------------------ ------------------------------ ---------- ---------- ---------- ---------- -------
INDX                           BINGNORFER                              0  524288000          0      64000 OK
USERS                          BINGNORFER                      320864256  524288000      39168      64000 OK
DFLT                           BINGNORFER                              0          0          0          0 OK
SYSAUX                         BINGNORFER                              0          0          0          0 OK
SYSTEM                         BINGNORFER                              0          0          0          0 OK
UNDOTBS1                       BINGNORFER                              0          0          0          0 OK

6 rows selected.

SQL>

And for releases earlier than 9i:

SQL> select
  2  s.tablespace_name,
  3  nvl(q.username, upper('&&1')) username,
  4  nvl(q.bytes,0) bytes,
  5  decode(q.MAX_BYTES, -1, s.ttl_bytes, nvl(q.max_bytes,0)) max_bytes,
  6  nvl(q.BLOCKS,0) blocks,
  7  decode(q.MAX_BLOCKS, -1, s.ttl_blocks, nvl(q.max_blocks,0)) max_blocks,
  8  decode(sign(decode(decode(q.max_bytes, -1, s.ttl_bytes, q.max_bytes) - q.bytes, 0, -1, 1)), -1,'ALARM', 'OK') status
  9  from (
 10     select
 11     TABLESPACE_NAME,
 12     USERNAME,
 13     BYTES,
 14          max_bytes,
 15     blocks,
 16     MAX_BLOCKS
 17     from dba_ts_quotas
 18     where username = upper('&&1')
 19  ) q, (
 20     select tablespace_name, sum(bytes) ttl_bytes, sum(blocks) ttl_blocks
 21     from dba_data_files
 22     group by tablespace_name
 23  ) s
 24  where q.tablespace_name (+)= s.tablespace_name
 25  order by q.username, s.tablespace_name;

TABLESPACE_NAME                USERNAME         BYTES   MAX_BYTES     BLOCKS  MAX_BLOCKS STATU
------------------------------ ----------- ---------- ----------- ---------- ----------- -----
TOOLS                          BING                 0    10485760          0        1280 OK
USERS                          BING          17235968  2899247104       2104      353912 OK
EXAMPLE                        BING                 0           0          0           0 OK
STATDATA                       BING                 0           0          0           0 OK
SYSAUX                         BING                 0           0          0           0 OK
SYSTEM                         BING                 0           0          0           0 OK
UNDOTBS1                       BING                 0           0          0           0 OK

7 rows selected.

SQL>

Without having CASE the indicator is rudimentary, stating that either there IS space to use or there ISN’T space available. But, it works as well as it can given the limitations of the 8.1.7 and earlier SQL engines.

How much ‘wood’ can a ‘woodchuck’ chuck? That depends upon how much ‘wood’ is left to chuck. And you’ll know exactly in releases 9.0.1 and later. And, well, approximately in releases 8.1.7 and earlier (talk about ancient history…), which is, of course, better than not knowing at all.

So if you’re still on 8.1.7.4 seriously consider upgrading to 10.2.0 or 11.1. [If you’re still on 7.3 or 8.0, maybe you should open a museum.] Because you’d really rather know exactly how much space your users have left.

You would.

Really.

March 5, 2009

Expect The Unexpected

Filed under: dates — dfitzjarrell @ 14:21

For years developers have been warned to not rely upon default date formats when writing application code, which is sound advice indeed. However, maybe that should be expanded to include not relying upon what one might expect when passing partial date strings to TO_DATE since the results might not match the desired outcome. Let’s look at some examples and see where Oracle may not do what you think it should.

I think by now most people know that if the time is not supplied to TO_DATE it defaults to midnight:

SQL>
SQL> --
SQL> -- By default Oracle sets the time to midnight
SQL> -- if the time is not supplied to the
SQL> -- TO_DATE function
SQL> --
SQL> select TO_DATE('03012009','DDMMYYYY') from dual;

TO_DATE('03012009',
-------------------
01-03-2009 00:00:00

SQL>

so this should be expected behaviour. Let’s look at what happens when the day is not supplied:

SQL> --
SQL> -- Oracle defaults to the first day of the month
SQL> -- when the day is not provided to TO_DATE
SQL> --
SQL> select TO_DATE('032009','MMYYYY') from dual;

TO_DATE('032009','M
-------------------
03-01-2009 00:00:00

SQL>

So far this is behaviour to be expected. What happens when only the year is supplied? Let’s find out:

SQL> --
SQL> -- Oracle defaults to the first day
SQL> -- of the current month when neither month nor day
SQL> -- is specified
SQL> --
SQL> select TO_DATE('1995','YYYY') from dual;

TO_DATE('1995','YYY
-------------------
03-01-1995 00:00:00

SQL>

Hmmm, I would have expected the current month and day to be returned. Now let’s pass the day and the year to TO_DATE:

SQL> --
SQL> -- Oracle defaults to the current month
SQL> -- when it is not provided
SQL> --
SQL> -- We illustrate this by passing the day and the
SQL> -- year to TO_DATE
SQL> --
SQL> select TO_DATE('092008','DDYYYY') from dual;

TO_DATE('092008','D
-------------------
03-09-2008 00:00:00

SQL>

And that’s what I would expect. Passing in the day and the month causes Oracle to use the current year:

SQL> --
SQL> -- The default is to assume the current year if it
SQL> -- is not provided
SQL> --
SQL> select TO_DATE('0712','MMDD') from dual;

TO_DATE('0712','MMD
-------------------
07-12-2009 00:00:00

SQL>

and that’s, again, what I would expect. Now let’s pass in only the day:

SQL> --
SQL> -- Assume the current month and year when only
SQL> -- the day is provided
SQL> --
SQL> select TO_DATE('23','DD') from dual;

TO_DATE('23','DD')
-------------------
03-23-2009 00:00:00

SQL>

Now let’s provide only the time:

SQL> --
SQL> -- Strangeness abounds, as when only the time is provided
SQL> -- Oracle defaults to the current month but assumes
SQL> -- the first day of that month
SQL> --
SQL> select to_date('11:00:00','hh24:mi:ss')
  2  from dual;

TO_DATE('11:00:00',
-------------------
03-01-2009 11:00:00

SQL>

I would have thought Oracle would presume the current month and day for that example, but what I think and what Oracle does are two different things.

So, Oracle can do the expected, depending upon which part of the date string is supplied. It can also do the unexpected, and that can be unnerving if you’re trying to troubleshoot an application and can’t understand why the date arithmetic is off:

SQL> --
SQL> -- This doesn't return the expected result
SQL> -- because Oracle assumes the first day of the
SQL> -- month, not the current day
SQL> --
SQL> select sysdate - to_date('07:00:00','hh24:mi:ss')
  2  from dual;

SYSDATE-TO_DATE('07:00:00','HH24:MI:SS')
----------------------------------------
                              4.08762731

SQL>

Fixing that problem means rewriting the query a bit:

SQL> --
SQL> -- Let's get the result we expected
SQL> --
SQL> select sysdate - to_date(to_char(sysdate, 'MM-DD-RRRR')||' 07:00:00','mm-dd-rrrr hh24:mi:ss')
  2  from dual;

SYSDATE-TO_DATE(TO_CHAR(SYSDATE,'MM-DD-RRRR')||'07:00:00','MM-DD-RRRRHH24:MI:SS'
--------------------------------------------------------------------------------
                                                                      .087627315

SQL>

If you’re not certain what Oracle will return from a function call you should test the code before assuming anything as the examples above attest. We certainly didn’t get the second result from the code in the prior example simply because Oracle didn’t return the default data as we thought it should.

Expect the unexpected, and nothing should be a surprise. Well, at least not an unpleasant one.

Blog at WordPress.com.