Who's online

There are currently 0 users and 31 guests online.

Recent comments

Oakies Blog Aggregator

Oak Table World, Collaborate 2017 Edition!


We weren't sure we'd get Oak Table World for Collaborate going, but Bryn made sure that it happened, (better thank him when you see him!)   We'll be in the South Seas C Ballroom all day Wednesday, so come learn database knowledge that everyone else is too frightened to talk about!

Time Speaker Title of Abstract
Wednesday, April 5    
8:30am - 9:30am Keynote – no sessions  
9:45am - 10:45am RAC Cache Fusion internals - Riyaj Shamsudeen Demos of RAC cache fusion using wireshark.
11:00am - 12:00pm Bryn Llewellyn Why Use PL/SQL?
12:00pm – 1:30pm Lunch- See Ted Slots below  
1:30pm - 2:30pm Tim Gorman dNFS
2:45pm - 3:45pm Kellyn Pot'Vin-Gorman Oracle vs. SQL Server- Indexing Features and Storage, View from the Multi-Platform World
3:45pm - 4:15pm Break – no sessions  
4:15pm - 5:15pm  Mauro Pagano  Something Cool with 10053 Trace Files, (or so he says! :))
Oakie Hour?  Panel with questions? BYOP, (Bring your own problem! Reception starts at 7pm; How much trouble can we get into??  
Ted Talks- 12pm-1:30pm 10 minutes long, six total slots Title of Abstract
12:05-12:15 Jonathan Gennick Learnings from SQL Server and Power BI: And Why I didn't Use Oracle
12:20-12:30 Bryn Llewellyn Ten rules for doing a PL/SQL performance experiment  

 Sponsors for this great event?  Check'em out!

Managing Inbound Traffic on an AWS Instance


“Enough already with the DHCP complaints!”  It’s Friday, so it means soon, we can stop worrying about such stuff and can get onto better things…

Until then, I’m sure other folks will be working with an AWS instance, go to log in after setting it up the initial time and want to know why the ssh connection that worked fine the day before is now hanging.  In fact, if anything has been done to renew their DHCP release and change their IP Address, this could occur.

There’s always more than one way to skin a cat and along with setting aliases for your favored IP address, the second option is to follow the next steps.

What’s Your Number?

So you can get to your instances via the EC2 console and use the Delphix admin console without any issue, but if you try to ssh into a source or target, (aka instances) the prompt just hangs.  The problem is that all that lovely automation that built out this incredible environment for the Delphix AWS Trial, also built out the security group setting for inbound traffic for you, tying the access to your IP address.  This information was entered in the terraform.tfvars file and the IP in this file no longer matches your new IP Address.

  • Get the new IP Address.  Number of methods- but I’m lazy and just type in “What is my ip address” in Google.

Update Your Security Group Rule

  1. In the the AWS EC2 Console, click on the instance you’re trying to log into.
  2. Click on the Description tab in the lower part of the screen. 300w, 768w, 1070w" sizes="(max-width: 501px) 100vw, 501px" data-recalc-dims="1" />

3. In this tab, you’ll see a listing for Security Groups.  Click on the group name to the right of Security Groups.

4. Click on Edit 300w, 768w, 1478w, 1200w" sizes="(max-width: 514px) 100vw, 514px" data-recalc-dims="1" />

5. You’ll see what was your previous IP address, (if you haven’t updating your terraform.tfvars since the build, you can verify this.)

6. Update the IP address with your new one and click on SAVE.

Now try to SSH into the box.  You’ll need to do this for any and all instances that you want to connect to via a terminal.

Hint hint:  This is also the location to add a new workstation that needs access to work on the instances.

Happy Friday!






Copyright © DBA Kevlar [Managing Inbound Traffic on an AWS Instance], All Right Reserved. 2017.

The post Managing Inbound Traffic on an AWS Instance appeared first on DBA Kevlar.

Announcing Trivadis Performance Days 2017

Trivadis Performance Days 2017

It is a great pleasure to announce the next Performance Days! This year the event will take place the 13-14 September in Zurich.

Given that detailed information about the event as well as online subscription are available at, in this short post I limit myself to thanking and pointing out who the speakers that accepted my invitation are:

With so many talented and knowledgeable speakers no doubt the event will be awesome! See you there.

Can You Say That An Oracle Database is nn% secure?

I often get this type of question from customers and also from people I speak to and even a few times by email. The question is "can you tell us how secure our database is?", is it 10% secure, is....[Read More]

Posted by Pete On 23/03/17 At 03:22 PM

min/max Upgrade

A question came up on the OTN database forum a little while ago about a very simple query that was taking different execution paths on two databases with the same table and index definitions and similar data. In one database the plan used the “index full scan (min/max)” operation while the other database used a brute force “index fast full scan” operation.

In most circumstances the starting point to address a question like this is to check whether some configuration details, or some statistics, or the values used in the query are sufficiently different to result in a significant change in costs; and the first simple procedure you can follow is to hint each database to use the plan from the opposite database to see if this produces any clues about the difference – it’s a good idea when doing this test to use one of the more verbose formatting options for the call to dbms_xplan.

In this case, though, the OP discovered a note on MoS reporting exactly the problem he was seeing:

Doc ID 2144428.1: Optimizer Picking Wrong ‘INDEX FAST FULL SCAN’ Plan vs Correct ‘INDEX FULL SCAN (MIN/MAX)’

which referred to


Conveniently the document suggested a few workarounds:

  • alter session set optimizer_features_enable = ‘’;
  • alter session set “_fix_control” = ‘13430622:off’;
  • delete object stats [Ed: so that dynamic sampling takes place … maybe a /*+ dynamic_sampling(alias level) */ hint would suffice].

Of the three options my preference would (at least in the short term) be the _fix_control one. Specifically, from the v$system_fix_control view, we can see that it addresses the problem very precisely with the description: “index min/max cardinality estimate fix for filter predicates”.

The example in the bug note showed a very simple statement (even more simple than the OP’s query which was only a single table query anyway), so I thought I’d build a model and run a few tests to see what was going on. Luckily, before I’d started work, one of the other members of the Oak Table network sent an email to the list asking if anyone knew how the optimizer was costing an example he’d constructed – and I’ve finally got around to looking at his example, and here’s the model and answer(s), starting with the data set:

rem     Script:         test_min_max.sql
rem     Dated:          March 2017
rem     Last tested

create table min_max_test nologging
with ids as (
        select /*+ Materialize */ rownum  id from dual connect by rownum <= 50000 -- > comment to protect formatting
line_nrs as (
        select /*+ Materialize */  rownum line_nr from dual connect by rownum <= 20 -- > comment to protect formatting
        id, line_nr ,rpad(' ', 800, '*') data
        line_nrs, ids
order by
        line_nr, id

                ownname          => user,
                tabname          =>'min_max_test',
                method_opt       => 'for all columns size 1'

create index mmt_ln_id on min_max_test (line_nr, id) nologging;
create index mmt_id    on min_max_test (id)          nologging;

The table has two critical columns: each id has 20 line_nr values associated with it, but the way the data was generated means that the line numbers for a given id are scattered across 20 separate table blocks.

There are two indexes – one on the id which will allow us to find all the rows for a given id as efficiently as possible, and one (slightly odd-looking in this context) that would allow us to find a specific row for a given line_nr and id very efficiently. Two things about these indexes – in a live application they should both be compressed on the first (only, in the case of index mmt_id) column, and secondly the necessity of the mmt_id index is questionable and it might be an index you could drop if you reversed the order of the columns in mmt_ln_id. The thing about these indexes, though, is that they allow us to demonstrate a problem. So let’s query the data – twice, hinting each index in turn:

set serveroutput off

        /*+ index(t(id)) */
        min_max_test t
        id = :b1

select * from table(dbms_xplan.display_cursor);

        /*+ index(t(line_nr, id)) */
        min_max_test t
        id = :b1

select * from table(dbms_xplan.display_cursor);

It’s fairly safe to make a prediction about the execution plan and cost of the first query – it’s likely to be a range scan that accesses a couple of branch blocks, a leaf block and 20 separate table blocks followed by a “sort aggregate” – with a cost of about 23.

It’s a little harder to make a prediction about the second query. The optimizer could infer that the min(line_nr) has to be close to the left hand section of the index, and could note that the number of rows in the table is the same as the product of the number of distinct values of the two separate columns, and it might note that the id column is evenly distributed (no histogram) across the data, so it might “guess” that it need only range scan all the entries for the first line_nr to find the appropriate id. So perhaps the optimizer will use the index min/max range scan with a cost that is roughly 2 branch blocks plus total leaf blocks / 20 (since there are 20 distinct values for line_nr); maybe it would divide the leaf block estimate by two because “on average” – i.e. for repeated random selections of value for id – it would have to scan half the leaf blocks. There were 2,618 leaf blocks in my index, so the cost should be close to either 133 or 68.

Here are the two plans – range scan first, min/max second:

select  /*+ index(t(id)) */  min(line_nr) from  min_max_test t where id = :b1
| Id  | Operation                            | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
|   0 | SELECT STATEMENT                     |              |       |       |    23 (100)|          |
|   1 |  SORT AGGREGATE                      |              |     1 |     8 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID BATCHED| MIN_MAX_TEST |    20 |   160 |    23   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN                  | MMT_ID       |    20 |       |     3   (0)| 00:00:01 |

Predicate Information (identified by operation id):
   3 - access("ID"=:B1)

select  /*+ index(t(line_nr, id)) */  min(line_nr) from  min_max_test t where  id = :b1
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
|   0 | SELECT STATEMENT            |           |       |       |    22 (100)|          |
|   1 |  SORT AGGREGATE             |           |     1 |     8 |            |          |
|   2 |   FIRST ROW                 |           |     1 |     8 |    22   (0)| 00:00:01 |
|*  3 |    INDEX FULL SCAN (MIN/MAX)| MMT_LN_ID |     1 |     8 |    22   (0)| 00:00:01 |

Predicate Information (identified by operation id):
   3 - filter("ID"=:B1)

Spot on with the estimate for the simple range scan – but what did we do wrong with the estimate for the min/max scan ? You might notice in the first example the “table access by rowid batched” and realise that this is running on 12c. Here’s the plan if I get if I set the optimizer_features_enable back to before running the second query again:

select  /*+ index(t(line_nr, id)) */  min(line_nr) from  min_max_test t where  id = :b1
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
|   0 | SELECT STATEMENT            |           |       |       |   136 (100)|          |
|   1 |  SORT AGGREGATE             |           |     1 |     8 |            |          |
|   2 |   FIRST ROW                 |           |     1 |     8 |   136   (1)| 00:00:01 |
|*  3 |    INDEX FULL SCAN (MIN/MAX)| MMT_LN_ID |     1 |     8 |   136   (1)| 00:00:01 |

Predicate Information (identified by operation id):
   3 - filter("ID"=:B1)

Using the optimizer model the plan has a cost that’s very close to our prediction – we’ll see why there’s a slight difference in a moment. If we set the optimizer_features_enable to the cost drops back to 22. So for our example will use the simple “index range scan” and an upgrade to (or higher) will switch to the “index full scan (min/max)”. If you look at the OTN posting the impact of the change in costing is exactly the other way around – uses the min/max path, uses the simple index range scan.

The techy bit

You really don’t need to know this – experimenting with the optimizer_features_enable (or _fix_control) will give you plans that show you all the numbers you need to see to check whether or not you’ve run into this particular problem – but if you’re interested here’s a little bit from the two 10053 trace files. We need only look at a few critical lines. From the costing for the min/max scan:

Index Stats::
  Index: MMT_ID  Col#: 1
  LVLS: 2  #LB: 2202  #DK: 50000  LB/K: 1.00  DB/K: 20.00  CLUF: 1000000.00  NRW: 1000000.00
  Index: MMT_LN_ID  Col#: 2 1
  LVLS: 2  #LB: 2618  #DK: 1000000  LB/K: 1.00  DB/K: 1.00  CLUF: 125000.00  NRW: 1000000.00

  Single Table Cardinality Estimation for MIN_MAX_TEST[T]
  Column (#1): ID(NUMBER)
    AvgLen: 5 NDV: 50536 Nulls: 0 Density: 0.000020 Min: 1.000000 Max: 50000.000000
  Table: MIN_MAX_TEST  Alias: T
    Card: Original: 1000000.000000  Rounded: 20  Computed: 19.787874  Non Adjusted: 19.787874

 ****** Costing Index MMT_LN_ID
  Access Path: index (Min/Max)
    Index: MMT_LN_ID
    resc_io: 135.000000  resc_cpu: 961594
    ix_sel: 1.000000  ix_sel_with_filters: 1.9788e-05
    Cost: 135.697679  Resp: 135.697679  Degree: 1

I was running so there were a few extra bits and pieces that I’ve deleted (mostly about SQL Plan Directives and in-memory). Critically we can see that the stats collection has a small error for the ID column – 50,536 distinct values (NDV) instead of exactly 50,000. This seems to have given us a cost for the expected index range of: 2 (blevel) + ceiling(2618 (leaf blocks) * 50536 / 1000000) = 2 + ceil(132.3) = 135, to which we add a bit for the CPU and get to 136. (Q.E.D.)

Then we switch to costing for

  Single Table Cardinality Estimation for MIN_MAX_TEST[T]
  Column (#1): ID(NUMBER)
    AvgLen: 5 NDV: 50536 Nulls: 0 Density: 0.000020 Min: 1.000000 Max: 50000.000000
  Table: MIN_MAX_TEST  Alias: T
    Card: Original: 1000000.000000  Rounded: 20  Computed: 19.787874  Non Adjusted: 19.787874

 ****** Costing Index MMT_LN_ID
  Access Path: index (Min/Max)
    Index: MMT_LN_ID
    resc_io: 21.787874  resc_cpu: 156872
    ix_sel: 1.000000  ix_sel_with_filters: 1.9788e-05
    Cost: 22.324608  Resp: 22.324608  Degree: 1

We still have the small error in the number of distinct values for id, so the estimated number of rows that we need to access from the table for a given id (before “aggregating” to find its minimum line_nr) is 19.787874 (Computed: / Non Adjusted:) rather than exactly 20. Notice, then, that the cost of using the index is 19.787874 + 2 which looks suspiciously like adding the blevel to the number of table blocks to get a cost and forgetting that we might have to kiss a lot of frogs before we find the prince. Basically, in this example at least, it looks like the costing algorithm has NOTHING to do with the mechanics of what actually has to happen at run-time.


This is only an initial probe into what’s going on with the min/max scan; there are plenty more patterns of data that would need to be tested before we could have any confidence that we had produced a generic model of how the optimizer does its calculations – the only thing to note so far is that there IS a big change as  you move from to later versions: the case on OTN showed the min/max scan disappearing on the upgrade, the example above shows the min/max disappearing on the downgrade – either change could be bad news for parts of a production system.

There are a couple of related bugs that might also be worth reviewing.


There is a note, though that this last bug was fixed in 12.1

Footnote 2

When experimenting, one idea to pursue as the models get more complex and you’re using indexes with more than two columns is to test whether the presence of carefully chosen column group statistics might make a difference to the optimizer’s estimates of cardinality (hence cost) of the min/max scan.


PFCLScan - A Security Scanner For Oracle Databases - New Website

Our software product PFCLScan can be used to assess your Oracle databases for security issues that could make your data vulnerable to loss or attack. PFCLScan initially had its own website, but since the restyle and redesign of our....[Read More]

Posted by Pete On 22/03/17 At 08:24 PM

Iceland is Awesome….and Colder than Utah

I ended up speaking at two events this last week.  Now if timezones and flights weren’t enough to confuse someone, I was speaking at both an Oracle AND a SQL Server event- yeah, that’s how I roll these days.

Utah Oracle User Group, (UTOUG)

I arrived last Sunday in Salt Lake, which is just a slightly milder weather and more conservative version of Colorado, to speak at UTOUG’s Spring Training Days Conference.  I love this location and the weather was remarkable, but even with the warm temps, skiing was still only a 1/2 hour drive from the city.  Many of the speakers and attendees took advantage of this opportunity by doing just that while visiting.  I chose to hang out with Michelle Kolbe and Lori Lorusso.  I had a great time at the event and although I was only onsite for 48hrs, I really like this event so close to my home state.

I presented on Virtualization 101 for DBAs and it was a well attended session.  I really loved how many questions I received and how curious the database community has become about how this is the key to moving to the cloud seamlessly.

There are significant take-aways from UTOUG.  The user group, although small, is well cared for and the event is using some of the best tools to ensure that they get the best bang for the buck.  It’s well organized and I applaud all that Michelle does to keep everyone engaged.  It’s not an easy endeavor, yet she takes this challenge on with gusto and with much success.

SQL Saturday Iceland

After spending Wednesday at home, I was back at the airport to head to Reykjavik, Iceland for their SQL Saturday.  I’ve visited Iceland a couple times now and if you aren’t aware of this, IcelandAir offers up to 7 day layovers to visit Iceland and then you can continue on to your final destination.  Tim and I have taken advantage of this perk on one of our trips to OUGN, (Norway) and it was a great way to visit some of this incredible country.  When the notification arrived for SQL Saturday Iceland, I promptly submitted my abstracts and crossed my fingers.  Lucky for me,  accepted my abstract and I was offered the chance to speak with this great SQL Server user group.

After arriving before 7am on Friday morning at Keflavik airport, I realized that I wouldn’t have a hotel room ready for me, no matter how much I wanted to sleep.  Luckily there is a great article on the “I Love Reykjavik” site offering inside info on what to do if you do show up early.  I was able to use the FlyBus to get a shuttle directly to and from my hotel, (all you have to do is ask the front desk to call them the night before you’re leaving and they’ll pick you back up in front of your hotel 3 hrs before your flight.)  Once I arrived, I was able to check in my bags with their front desk and headed out into town.

I stayed at Hlemmur Square, which was central to the town and the event and next to almost all of the buses throughout the city.  The main street in front of it, Laugavegur, is one of the main streets that runs East-West and is very walkable.  Right across this street from the hotel was a very “memorable” museum, the Phallilogical Museum.  I’m not going to link to it or post any pictures, but if you’re curious, I’ll warn you, it’s NSFW, even if it’s very, uhm…educational.  It was recommended by a few folks on Twitter and it did ensure I stayed awake after only 2 hours of sleep in 24 hours!

As I wandered about town, there are a few things you’ll note about Iceland-  the murals of graffiti is really awesome and Icelandic folks like good quality products-  the stores housed local and international goods often made from wool, wood, quality metal and such. The city parliment building is easily accessible and it’s right across from the main shopping area and new city development. 300w, 768w, 1400w, 1200w" sizes="(max-width: 455px) 100vw, 455px" data-recalc-dims="1" />

On Saturday, I was quick to arrive at Iceland’s SQL Saturday, as I had a full list of sessions I wanted to attend.  I was starting to feel the effects of Iceland weather on my joints, but I was going to make sure I got the most out of the event.  I had connected with a couple of the speakers at the dinner the night before, but with jet lag, you hope you’ll make a better impression on the day of the event.

I had the opportunity to learn about the most common challenges with SQL Server 2016 and that Dynamic Data Masking isn’t an enterprise solution.  Due to lacking discovery tools, the ability to join to non-masked objects and common values, (i.e. 80% of data is local and the most common location value would easily be identified, etc.) the confidential data of masked objects could be identified.

I also enjoyed an introduction to containers with SQL Server and security challenges.  The opening slide from Andy says it all: 300w" sizes="(max-width: 455px) 100vw, 455px" data-recalc-dims="1" />

Makes you proud to be an American, doesn’t it? </p />

    	  	<div class=


One of the difficulties with trouble-shooting is that’s it very easy to overlook, or forget to go hunting for, the little details that turn a puzzle into a simple problem. Here’s an example showing how you can read a bit of an AWR report and think you’ve found an unpleasant anomaly. I’ve created a little model and taken a couple of AWR snapshots a few seconds apart so the numbers involved are going to be very small, but all I’m trying to demonstrate is a principle. So here’s a few lines of one of the more popular sections of an AWR report:

SQL ordered by Gets                       DB/Inst: OR32/or32  Snaps: 1754-1755
-> Resources reported for PL/SQL code includes the resources used by all SQL
   statements called by the code.
-> %Total - Buffer Gets   as a percentage of Total Buffer Gets
-> %CPU   - CPU Time      as a percentage of Elapsed Time
-> %IO    - User I/O Time as a percentage of Elapsed Time
-> Total Buffer Gets:         351,545
-> Captured SQL account for   65.0% of Total

     Buffer                 Gets              Elapsed
      Gets   Executions   per Exec   %Total   Time (s)  %CPU   %IO    SQL Id
----------- ----------- ------------ ------ ---------- ----- ----- -------------
      8,094          20        404.7    2.3        0.0 114.1   2.3 017r1rur8atzv
Module: SQL*Plus
UPDATE /*+ by_pk */ T1 SET N1 = 0 WHERE ID = :B1

We have a simple update statement which, according to the hint/comment (that’s not a real hint, by the way) and guessing from column names, is doing an update by primary key; but it’s taking 400 buffer gets per execution!

It’s possible, but unlikely, that there are about 60 indexes on the table that all contain the n1 column; perhaps there’s a massive read-consistency effect going on thanks to some concurrent long-running DML on the table; or maybe there are a couple of very hot hotspots in the table that are being constantly modified by multiple sessions; or maybe the table is a FIFO (first-in, first-out) queueing table and something funny is happening with a massively sparse index.

Let’s just check, first of all, that the access path is the “update by PK” that the hint/comment suggests (cut-n-paste):

SQL> select * from table(dbms_xplan.display_cursor('017r1rur8atzv',null));

SQL_ID  017r1rur8atzv, child number 0
UPDATE /*+ by_pk */ T1 SET N1 = 0 WHERE ID = :B1

Plan hash value: 1764744892

| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
|   0 | UPDATE STATEMENT   |       |       |       |     3 (100)|          |
|   1 |  UPDATE            | T1    |       |       |            |          |
|*  2 |   INDEX UNIQUE SCAN| T1_PK |     1 |    14 |     2   (0)| 00:00:01 |

Predicate Information (identified by operation id):
   2 - access("ID"=:B1)

The plan is exactly as expected – so where do we look next to find out what’s going on? I’m a great believer in trying to make sure I have as much relevant information as possible; but there’s always the compromise when collecting information that balances the benefit of the new information against the difficulty of gathering it – sometimes the information that would be really helpful is just too difficult, or time-consuming, to collect.

Fortunately, in this case, there’s a very quick easy way to enhance the information we’ve got so far. The rest of the AWR report – why not search for that SQL_ID in the rest of the report to see if that gives us a clue ? Unfortunately the value doesn’t appear anywhere else in the report. On the other hand there’s the AWR SQL report (?/rdbms/admin/awrsqrpt.sql – or the equivalent drill-down on the OEM screen), and here’s a key part of what it tells us for this statement:

Stat Name                                Statement   Per Execution % Snap
---------------------------------------- ---------- -------------- -------
Elapsed Time (ms)                                36            1.8     0.0
CPU Time (ms)                                    41            2.0     0.1
Executions                                       20            N/A     N/A
Buffer Gets                                   8,094          404.7     2.3
Disk Reads                                        1            0.1     0.0
Parse Calls                                      20            1.0     0.4
Rows                                          2,000          100.0     N/A
User I/O Wait Time (ms)                           1            N/A     N/A
Cluster Wait Time (ms)                            0            N/A     N/A
Application Wait Time (ms)                        0            N/A     N/A
Concurrency Wait Time (ms)                        0            N/A     N/A
Invalidations                                     0            N/A     N/A
Version Count                                     1            N/A     N/A
Sharable Mem(KB)                                 19            N/A     N/A

Spot the anomaly?

We updated by primary key 20 times – and updated 2,000 rows!

Take another look at the SQL – it’s all in upper case (apart from the hint/comment) with a bind variable named B1 – that means it’s (probably) an example of SQL embedded in PL/SQL. Does that give us any clues ? Possibly, but even if it doesn’t we might be able to search dba_source for the PL/SQL code where that statement appears. And this is what it looks like in the source:

        forall i in 1..m_tab.count
                update  /*+ by_pk */ t1
                set     n1 = 0
                where   id = m_tab(i).id

It’s PL/SQL array processing – we register one execution of the SQL statement while processing the whole array, so if we can show that there are 100 rows in the array the figures we get from the AWR report now make sense. One of the commonest oversights I (used to) see in places like the Oracle newsgroup or listserver was people reporting the amount of work done but forgetting to consider the equally important “work done per row processed”. To me it’s also one of the irritating little defects with the AWR report – I’d like to see “rows processed” in various of the “SQL ordered by” sections of the report (not just the “SQL ordered by Executions” section), rather than having to fall back on the AWR SQL report.


If you want to recreate the model and tests, here’s the code:

rem     Script:         forall_pk_confusion.sql
rem     Author:         Jonathan Lewis
rem     Dated:          Mar 2017
rem     Last tested

create table t1
with generator as (
        select  --+ materialize
                rownum id
        from dual
        connect by
                level <= 1e4
        cast(rownum as number(8,0))                     id,
        2 * trunc(dbms_random.value(1e10,1e12))         n1,
        cast(lpad('x',100,'x') as varchar2(100))        padding
        generator       v1,
        generator       v2
        rownum <= 1e6 -- > comment to avoid WordPress format problem

                ownname          => user,
                tabname          =>'T1', 
                method_opt       => 'for all columns size 1'

alter table t1 add constraint t1_pk primary key(id);


        cursor c1 is
        select  id
        from    t1
        where   mod(id,10000) = 1

        type c1_array is table of c1%rowtype index by binary_integer;
        m_tab c1_array;


        open c1;

        fetch c1
        bulk collect
        into m_tab

        dbms_output.put_line('Fetched: ' || m_tab.count);

        close c1;

        forall i in 1..m_tab.count
                update  /*+ by_pk */ t1
                set     n1 = 0
                where   id = m_tab(i).id

        dbms_output.put_line('Updated: ' || sql%rowcount);


        v$sql   sql,
        table(dbms_xplan.display_cursor(sql.sql_id, sql.child_number)) v
        sql_text like 'UPDATE%by_pk%'

        executions, rows_processed, disk_reads, buffer_gets
from    v$sql  
where   sql_id = '017r1rur8atzv'

End of an era …

Four years ago I wrote about a little volunteer project that my partner did.  A small association that provided outdoor experiences and facilities for kids with physical impairments needed a system to record member and volunteer details, plus a few other bits and pieces.  We built an Apex solution running on XE.  This week, they became part of a larger government initiative, and thus their Apex application was no longer needed and the information migrated to a centralised service.  There was a tinge of sadness about that, but I also was pleased with the outcomes of this “project” namely:

  • It ran for 4 years with virtually never an outage besides those related to power etc.  (After all, their “server” was just a PC in the secretary’s office Smile)
  • Their PC’s etc went through several iterations of patching, upgrades, replacements etc and the system was unaffected because it was entirely run in the browser
  • We never had a single issue with the database
  • Minimal maintenance needed.  In fact, the only “serious” bit of work needed after go live was when we discovered that their external drive (where we stored our database backups) was from time to time removed to be used for offsite file transfers, and when it was re-attached they would assign it a new drive letter.  So we adjusted our backup script to cycle through drive letters to “find” the disk and adjust the RMAN backup details accordingly.

That’s one of the great things with Apex, and a database-centric model.  It is just so well insulated from all the things that change most frequently, that is, those elements closest to the client.

So yesterday I took a final datapump export of the system as a “just in case” measure, and uninstalled the application and its dependencies from the PC.  But for four years, they had a successful application that provided all of their data entry needs and all of their reporting needs, and besides checking an occasional email to ensure the backups were working ok, took very little of my time. And surely that’s what all IT applications “aspire” to be – stuff that just plain works.

It never let them down and never cost them a cent.  You can’t tick any more boxes than that Smile

Refreshing VDB With Sales History Data

Now that I’ve loaded a ton of transactions and did a bunch of work load on my source database with the SH sample schema and Swingbench, I’ve noted how little impact to the databases using different cloud tools, (which will come in a few later posts) now I’m going to show you how easy it is to create a new VDB from all of this, WITH the new SH data included.  During all of this time, the primary users of my Delphix VDB, (virtualized databases) would have been working in the previous iage, but someone wants that new SH schema now that my testing has completed.

To do this, I open up my Delphix admin console, (using the IP address for the Delphix Engine from the AWS Trail build output), log in as delphix_admin and open up the Source group to access the Employee Oracle 11g database, (aka ORCL.)

I know my new load is complete on the ORCL database and need to take a new snapshot to update the Delphix Engine outside of the standard refresh interval, (I’m set at the default of every 24 hrs.)  Access this by clicking on the Configuration tab and to take a snapshot, I simply click on the camera icon. 300w, 768w, 1046w" sizes="(max-width: 481px) 100vw, 481px" data-recalc-dims="1" />

A snapshot will take a couple seconds, as this is a very, very small database, (2.3G) and then you can click on Timeflow to view the new snapshot available for use.  Ensure the new snapshot is chosen by moving the slider all the way to the right and look at the timestamp, ensuring it’s the latest, matching your recent one. 300w, 768w, 1104w" sizes="(max-width: 470px) 100vw, 470px" data-recalc-dims="1" />

Click on Provision and it will default to the Source host, change to the target, update to a new, preferred database name, (if you don’t like the default) and then you may have to scroll down to see the Next button to go through the subsequent steps in the wizard.  I know my Macbook has a smaller screen and I do have to scroll to see the Next button.  After you’ve made any other changes, click on Finish and let the job run.  Don’t be surprised by the speed that a VDB is provisioned-  I know it’s really fast, but it really did create a new VDB!

Now that we have it, let’s connect to it from SQL*Plus and check prove that we got the new SH schema over.

Using the IP Address for the Linux Target that was given to use in our AWS Trial build, let’s connect:

ssh delphix@

Did you really just create a whole new VDB?

[delphix@linuxtarget ~]$ ps -ef | grep pmon
delphix   1148  1131  0 18:57 pts/0    00:00:00 grep pmon
delphix  16825     1  0 Mar09 ?        00:00:06 ora_pmon_devdb
delphix  16848     1  0 Mar09 ?        00:00:06 ora_pmon_qadb
delphix  31479     1  0 18:30 ?        00:00:00 ora_pmon_VEmp6C0

Yep, there it is…

Now let’s connect to it.

Set our environment:

. 11g.env

Set the ORACLE_SID to the new VDB

export ORACLE_SID=VEmp6C0

Connect to SQL*Plus as our SH user using the password used in our creation on the source database, ORCL:

$ sqlplus sh

Enter password: 
Connected to:
Oracle Database 11g Enterprise Edition Release - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> select object_type, count(*) from user_objects
  2  group by object_type;


------------------- ----------
LOB                 2
DIMENSION           5
INDEX               30
VIEW                1
TABLE               16

8 rows selected.

SQL> select table_name, sum(num_rows) from user_tab_statistics
  2  where table_name not like 'DR$%'
  3  group by table_name
  4  order by table_name;


------------------------------ -------------
CAL_MONTH_SALES_MV             48
CHANNELS                       5
COSTS                          164224
COUNTRIES                      23
CUSTOMERS                      55500
FWEEK_PSCAT_SALES_MV           11266
PRODUCTS                       72
PROMOTIONS                     503
SALES                          1837686
TIMES                          1826

12 rows selected.

Well, lookie there, the same as the source database we loaded earlier... </p />

    	  	<div class=