Thursday, March 31, 2016

Beginner's mistakes in PL/SQL - Part 1

Before a month or so, I was given an interesting PL/SQL code for tuning. Its function was to read certain data from database and then export them into a flat file. I was told that customer is complaining that time to export usual batch of data (not very large) is unacceptable. So it was typical "make it faster" problem.

As I was reading through and analyzing the code, I've decided I'm going to make a blog from that as it had all the nice mistakes in one place.

All we'll do in our first post is that we are going to setup our example. After that, in the mean time, you can read the code and try to identify mistakes and slow parts. Also you can try to construct your own faster solution and then you can compare it with next blog posts.

The code below is given for demonstration purposes only and replicates all the mistakes and tuning issues original program had. I’ve also cut some code to make it more readable.

DDL script to setup our data model and populate it with data:

Package specification and body:

To test it, just run following code:

SET SERVEROUTPUT ON;
BEGIN
 data_to_ascii.load_tmp_table;
 data_to_ascii.create_blob;
END;
/

Continue to Part 2

Friday, March 4, 2016

Join predicate push down issue

This blog post is about very interesting issue with Oracle Optimizer which I do dare call a bug. To be honest with you I’ve spend couple of evenings on it since it was driving me crazy :). I knew what was going on but I wanted to force the Oracle to do the right thing. In the end I had to revert to backup solution.



Now, let’s start from the beginning. I’m providing all the things you need to be able to play with it on your own. Problem was spotted on Oracle 11.2.0.4.0. Here is the script to prepare data model for the demonstration:

CREATE OR REPLACE FORCE TYPE number_object IS OBJECT
(
 n NUMBER
);
/

CREATE OR REPLACE FORCE TYPE number_table AS TABLE OF number_object
/

CREATE TABLE list_agg_overflow
(
 n NUMBER,
 v VARCHAR2(1000)
);
/

CREATE INDEX list_agg_overflow_ix ON list_agg_overflow(n);
/

CREATE TABLE list_agg_overflow_output
(
 n NUMBER,
 v VARCHAR2(4000)
);
/

CREATE GLOBAL TEMPORARY TABLE list_agg_overflow_tmp
(
 n NUMBER
);
/

DECLARE
  v_dummy VARCHAR2(1000) := LPAD('X',1000,'X');
BEGIN
  --
  FOR i IN 1 .. 2
  LOOP
    INSERT INTO list_agg_overflow VALUES (1, v_dummy);
  END LOOP;
  --
  FOR i IN 1 .. 10
  LOOP
   INSERT INTO list_agg_overflow VALUES (2, v_dummy);
  END LOOP;
  --
  dbms_stats.gather_table_stats(null, 'list_agg_overflow');
  --
  COMMIT;
END;
/

As you can see, we have a list_agg_overflow table, which has 2 rows of id 1 and 10 rows of id 2. Each row contains a text of length 1000 characters. This is important since we are going to exploit it a little bit later. Now we run following anonymous PL/SQL block:

DECLARE
  v_output VARCHAR2(16384);
BEGIN
  --
  INSERT INTO list_agg_overflow_tmp VALUES (1);
  --
  INSERT INTO list_agg_overflow_output
  SELECT
    /*+ qb_name(main) leading(n_list a j) dynamic_sampling(0) */
    a.n,
    j.v
  FROM list_agg_overflow a,
       list_agg_overflow_tmp n_list,
       (SELECT
          /*+ qb_name(agg) push_pred */
          n,
          LISTAGG(v, ',') WITHIN GROUP (ORDER BY n) v
        FROM list_agg_overflow
        GROUP BY n
       ) j
  WHERE a.n = n_list.n
    AND a.n = j.n(+);
  ROLLBACK;
END;
/

We run the script with no error as expected. PL/SQL block is fairly simple. Basically we insert a list of IDs (column n) into our temporary table. Point of the query is to pick that list and join it on table a (as nested loop) and then join the result onto our subquery which does aggregate and creates a concatenation of text based on ID. We do want to push join predicate into subquery agg and it’s definitely possible, since we are aggregating by column we want push inside. Last join was outer and I left it there. It has no impact on query output (in original query it had) or our problem.

Here we see execution plan which looks as we want it to look:



Now let’s make a little change (and that’s how the query looked in 1st place). Let’s assume we have our list of IDs in an array. We can definitely do that and there might be a very good reason why to do so. Our block would look like this:

DECLARE
  v_list_tab number_table := number_table();
  v_output VARCHAR2(16384);
BEGIN
  --
  v_list_tab.EXTEND;
  v_list_tab(v_list_tab.FIRST) := number_object(1);
  --
  INSERT INTO list_agg_overflow_output
  SELECT
    /*+ qb_name(main) */
    a.n,
    j.v
  FROM list_agg_overflow a,
       TABLE(v_list_tab) n_list,
       (SELECT
          /*+ qb_name(agg) push_pred */
          n,
          LISTAGG(v, ',') WITHIN GROUP (ORDER BY n) v
        FROM list_agg_overflow
        GROUP BY n
       ) j
  WHERE a.n = n_list.n
    AND a.n = j.n(+);
  ROLLBACK;
END;
/

Now let’s run it and see this:

Error report -
ORA-01489: result of string concatenation is too long
ORA-06512: at line 9
01489. 00000 -  "result of string concatenation is too long"
*Cause:    String concatenation result is more than the maximum size.
*Action:   Make sure that the result is less than the maximum size.

Oook? Now only way this error could have happend is, that our join predicate was not pushed. LISTAGG can concatenate only up to 4000 characters and ID = 2 has 10 rows so it forces LISTAGG to overflow. Let’s check the execution plan:



Yeap … no push.  Now you can try to fight it but only way I found around it ether to say … screw array I’m going to use temporary table or do a partial step forward and change the query to this (IN can be ofcourse done as join):

DECLARE
  v_list_tab number_table := number_table();
  v_output VARCHAR2(16384);
BEGIN
  --
  v_list_tab.EXTEND;
  v_list_tab(v_list_tab.FIRST) := number_object(1);
  --
  INSERT INTO list_agg_overflow_output
  SELECT
    /*+ qb_name(main) */
    a.n,
    j.v
  FROM list_agg_overflow a,
       TABLE(v_list_tab) n_list,
       (SELECT
          /*+ qb_name(agg) push_pred */
          n,
          LISTAGG(v, ',') WITHIN GROUP (ORDER BY n) v
        FROM list_agg_overflow
        WHERE n IN (SELECT n FROM TABLE(v_list_tab))
        GROUP BY n
       ) j
  WHERE a.n = n_list.n
    AND a.n = j.n(+);
  ROLLBACK;
END;
/

Now there must be a reason for that. Why is Oracle pushing join predicate when we use temporary table and not if we use array? Now I believe it is an Optimizer bug. I’ve searched Oracle Support but I haven’t found anything about it. Might be that I used wrong key words. Anyway, to back my claim up I’m presenting except from Optimizer trace files which I made. 

Trace with temporary table:



Now with array:



Well of course it’s possible. He has done it before. The only reason that comes to my mind is, that Optimizer thinks it’s not possible, is a BUG in code ... or some nice FEATURE :).

However what you should take from this is not to not use arrays in SQL but to be more on guard when using them in some special cases.

Sunday, February 28, 2016

[java.lang.ClassNotFoundException][oracle.bpel.services.workflow.task.impl.TaskService.startup][soa-infra]

It’s not very often I have to deal with administration of WebLogic and SOA. However I do administer small installation for development purposes in one company. 

One of the servers crushed few dayes ago and from that moment we were unable to correctly start SOA infrastructure.

Main log showed very strange error:



I tried to search the web with very little success. Later I’ve got a "great" idea “Hey, let’s check diagnostic logs!” …. Next time I’ll do it right at the start. The error in the log was as follows:



Well that's much more precise! Now if you do search for that, you’ll probably get to Oracle Support Doc ID 1380835.1 - SOA/BPM: Solving MDS-00054 error preventing soa-infra to start correctly. Basically what you are dealing with here is incorrectly deployed component and until the problem is fixed; your soa infra will not come up. And the only way  to deal with it when your soa infra is not up is by removing it. There is a nice guide on how to remove component which is causing the problem:

Check the SOA logs and determine which composite is causing the problem and then follow the below process to undeploy the composite by editing deployed-composites.xml:

1. Download and copy the ShareSoaInfraPartition.ear file to $MIDDLEWARE_HOME/oracle_common/common/bin

2. cd to $MIDDLEWARE_HOME/oracle_common/common/bin and run wlst.sh

3.  Connect to a SOA server:

wls:/offline> connect()
Please enter your username :weblogic
Please enter your password :
Please enter your server URL [t3://localhost:7001] :server
Connecting to t3://server:7014 with userid weblogic ...
Successfully connected to Admin Server 'AdminServer' that belongs to domain 'name'.

4. run the below command to deploy ShareSoaInfraPartition.ear to the server:

wls:/vzpsoa/serverConfig> deploy('ShareSoaInfraPartition','ShareSoaInfraPartition.ear',upload='true')
Deploying application from /opt/oracle/middleware/soa111/oracle_common/common/bin/ShareSoaInfraPartition.ear to targets  (upload=true) ...
<Feb 26, 2016 12:28:50 PM CET> <Info> <J2EE Deployment SPI> <BEA-260121> <Initiating deploy operation for application, ShareSoaInfraPartition [archive: /opt/oracle/middleware/soa111/oracle_common/common/bin/ShareSoaInfraPartition.ear], to AdminServer .>
.......Completed the deployment of Application with status completed
Current Status of your Deployment:
Deployment command type: deploy
Deployment State       : completed
Deployment Message     : [Deployer:149194]Operation 'deploy' on application 'ShareSoaInfraPartition' has succeeded on 'AdminServer'

5. Now run the below command by changing the "toLocation" ('/fmw11g/fmw1115/Middleware' is some location path on SOA machine)

wls:/vzpsoa/serverConfig> exportMetadata(application='ShareSoaInfraPartition',server='AdminServer',toLocation='/opt/oracle/middleware/soa111',docs='/deployed-composites/deployed-composites.xml')
Location changed to domainRuntime tree. This is a read-only tree with DomainMBean as the root.
For more help, use help(domainRuntime)

Executing operation: exportMetadata.

Operation "exportMetadata" completed. Summary of "exportMetadata" operation is:
1 documents successfully transferred.
List of documents successfully transferred:

/deployed-composites/deployed-composites.xml

6. A deployed-composites folder will be created at "toLocation" path with deployed-composites.xml in it

7. Delete the composite which is causing the problem and save the file

For example, the MediatorTest composite:
<composite-series name="default/MediatorTest" default="default/MediatorTest!1.0">
<composite-revision dn="default/MediatorTest!1.0" state="on" mode="active" location="dc/soa_58b98be8-9ec8-41af-bb83-590f6004d1aa">
<composite dn="default/MediatorTest!1.0*soa_58b98be8-9ec8-41af-bb83-590f6004d1aa" deployedTime="2011-11-17T09:01:54.750+05:30"/>

8. Now run the below command by changing the "fromLocation" (this should be the same location as previous)

wls:/vzpsoa/serverConfig> importMetadata(application='ShareSoaInfraPartition',server='AdminServer',fromLocation='/opt/oracle/middleware/soa111',docs='/deployed-composites/deployed-composites.xml')

Executing operation: importMetadata.

Operation "importMetadata" completed. Summary of "importMetadata" operation is:
1 documents successfully transferred.
List of documents successfully transferred:

/deployed-composites/deployed-composites.xml

9. Now bounce your server and the composite will not be deployed by SOA when it comes up and hence that should bring your soa-infra up.

Note:  When you remove a composite that contains task definitions manually, the Task definition references to the composite are still in WFTASKMETADATA table. Even though the composite is not loaded during startup there will be an  exception when loading the task definition,

<Error> <oracle.soa.services.workflow.task>
<BEA-000000> <<.> Could not locate composite.

The references to WFTASKMETADATA for that specific composite/version can only be removed when a composite containing task definitions is cleanly undeployed using em/wlst.

Friday, February 19, 2016

Night-time quiz


Since I do not have anything I think would be interesting for you guys, I’ve created a little quiz for you.





You have a following table definition and data:

CREATE TABLE my_table
(
 id       NUMBER(3),
 item_cnt NUMBER(3),
 log_time DATE
)
/
CREATE UNIQUE INDEX my_table_ix1 ON my_table(id)
/
ALTER TABLE my_table ADD CONSTRAINT my_table_pk PRIMARY KEY (id)
/

INSERT INTO my_table VALUES (1, NULL, NULL)
/
INSERT INTO my_table VALUES (2, NULL, NULL)
/
INSERT INTO my_table VALUES (3, NULL, NULL)
/
COMMIT
/

Now we have following anonymous PL/SQL block:

DECLARE
 v_id my_table.id%TYPE := 1;
 --
 PROCEDURE ins (p_id my_table.id%TYPE)
 IS
 BEGIN
  INSERT INTO my_table VALUES (p_id, NULL, NULL);
END;
 --
BEGIN
 DELETE my_table WHERE id = v_id;
 ins (v_id);
END;
/

The question is … With what change to the code would you force a dead lock using same id?

Looking forward to your ideas …

UPDATE:

Thank you all for your comments :) He is the solotion:


DECLARE
 v_id my_table.id%TYPE := 1;
 --
 PROCEDURE ins (p_id my_table.id%TYPE)
 IS PRAGMA AUTONOMOUS_TRANSACTION;
 BEGIN
  INSERT INTO my_table VALUES (p_id, NULL, NULL);
END;
 --
BEGIN
 DELETE my_table WHERE id = v_id;
 ins (v_id);
END;
/

00060. 00000 - "deadlock detected while waiting for resource"
*Cause: Transactions deadlocked one another while waiting for resources.
*Action: Look at the trace file to see the transactions and resources
involved. Retry if necessary.

Tuesday, January 19, 2016

Index on sub partition column


I was about to name this blog post “Index mud” but no one would ever google it. So we’ll go with simple name “Index on sub partition column”.

You might call this another OTN post and you would be right. But I think it is a good example of usual – how to NOT do it.

The question was following:

Hi,
I am working on oracle 11g R2 on HP-UX platform.
We need to create a partition table have range (date) and sub partition by list (values) partition. I want to know if column we are using for sub partition (values ) is also need to index as we need to use that column in many of our select queries in where clause.

One member jump in and gave a simple answer:

Yes

.. And I was marked useful? Yap ... simple answer simple click. Not good guys, not good at all.

I presented a different opinion:

Since you are sub partitioning by list, I would expect there are not many values in list for particular sub partition? I would expect that selectivity will be quite low and index might not be of any use at all ... Unless you are selecting only indexed columns, for example.
With low selectivity partition prune will probably suffice. In your case I would do some testing with representative queries as index value is questionable here.

Ok. Let’s have a little presentation here. I’ve created table table1 with four indexes:

CREATE TABLE TABLE1
(
 P NUMBER(3),
 S NUMBER(1),
 filler VARCHAR2(100)
)
PARTITION BY RANGE (P) SUBPARTITION BY LIST (S)
(PARTITION P10 VALUES LESS THAN (11)
 (SUBPARTITION P10_3 VALUES (1,2,3),
  SUBPARTITION P10_6 VALUES (4,5,6),
  SUBPARTITION P10_9 VALUES (7,8,9)),
PARTITION P20 VALUES LESS THAN (21)
 (SUBPARTITION P20_3 VALUES (1,2,3),
  SUBPARTITION P20_6 VALUES (4,5,6),
  SUBPARTITION P20_9 VALUES (7,8,9)),
PARTITION P30 VALUES LESS THAN (31)
 (SUBPARTITION P30_3 VALUES (1,2,3),
  SUBPARTITION P30_6 VALUES (4,5,6),
  SUBPARTITION P30_9 VALUES (7,8,9)));

CREATE INDEX TABLE1_IX1 ON TABLE1(P,S) LOCAL;

TABLE1 has 379MB and TABLE1_IX1 has 54MB.

Each sub partition has around 100 000 rows for each list value (filler column has maximum size in each row). We are not going to go into the index details like testing different column combinations, etc. That is not important right now.

Now if you think about it, why would Oracle have any use for an index in following SQL?

SELECT * FROM TABLE1 WHERE P=10 AND S=4;

We are selecting all columns and selectivity of predicates in given sub partition is 33,3%. Let’s check the run statistics:



Ok. Now let’s force our index:

SELECT /*+ INDEX(TABLE1(P,S)) */ * FROM TABLE1 WHERE P=10 AND S=4;



Now as you can see (check Buffers and Reads), there is no point of using our index in this case as it is more expensive then sub partition full table scan.

Now there are queries, where index will be beneficial, like:

SELECT P,S FROM TABLE1 WHERE P=10 AND S=4;



So what should you take from this is, that you should not jump into rush conclusions when adding index but think it through. You should ask yourself questions like

  • What kind of queries will you use?
  • Which columns will you be most likely selecting?
  • What is selectivity of predicates?

It’s no point in wasting space and performance on maintaining index you don’t need.

Monday, January 4, 2016

ORA-02014:cannot select FOR UPDATE when using ROWNUM

9am, morning ...


I’m drinking my coffee and doing my morning run through my emails …

As I’m doing this, I’m regretting I have ever bought anything online …

Few emails not being spam contain some useful stuff for me. A friend of mine emailed me asking if I could help him with his problem.

He had a specific SQL with FOR UPDATE clause which was throwing ORA-02014 at him. Here is his SQL

SELECT tab.*
FROM
  (SELECT tab.ROWID AS rid, tab.*
   FROM changes tab
   WHERE state IN ('N', 'E')
   ORDER BY company, id, change_date
   ) tab
WHERE ROWNUM <= 10
FOR UPDATE OF tab.state;

Full error message is as follows

SQL Error: ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY, etc.
*Cause:    An attempt was made to select FOR UPDATE from a view which had not been merged because the view used syntax (for example, DISTINCT or GROUP BY) preventing the merge, or because of initialization file parameter settings.
*Action:   Determine the reason the view will not merge and, if possible, make changes so that it can.

Table was basically sort of a FIFO stack and he always wanted to process only first 10 oldest rows ordered by given columns. Because he is a very careful fellow he wanted to lock the rows as he was selecting them, because after that there was some processing of the data before changing them.

After few dead ends SQL which did the trick looks as follows

SELECT *
FROM changes
WHERE rowid IN
  (SELECT rid
   FROM
    (SELECT ROWID AS rid
     FROM changes
     WHERE state IN ('N', 'E')
     ORDER BY company, id, change_date
    )
  WHERE ROWNUM <= 10
  )
FOR UPDATE OF state;

I hope this post will save you some time … if not today then maybe some other day :)

Thursday, December 17, 2015

One does not simply create table with 300 columns


There was quite an interesting post on Oracle OTN, which is worth of this blog post and meme.

Problem was defined as follows:
I have a table which has 300 columns and keeps records 60,00,000 at this time. Records are increasing day by day. When I run a simple query like:

select * from table where store_no = 17

this query takes 30 to 60 seconds for fetching the data against this query which is to much time, while I'll use this table with more filters. Can anyone help me that how i can increase data fetching performance, i have also used a primary key indexing.

If you check supplied DDL against given SQL, you'll likely come to two points
  1. There is no index on column store_no and so Oracle will have to do full table scan
  2. ... 300 columns!?



My suggestion was to reconsider the data model. 300 columns are pretty rough and columns like IT_CLERK_ID2. IT_CLERK_ID3, ... , IT_CLERK_ID5 tend to be suspicious.

Reason for my suggestion was that Oracle will have to split rows in this table in two pieces because they have more than 255 columns. Interestingly enough, the split will be done from the end of the row. So first piece of the row will have first 45 columns and second piece will contain 255 columns. What should come to your mind now is that order of columns in such table is very important and you should place columns you are going to select as close as possible to the start of the row.

Result of this split will be extra CPU usage when selecting rows from second piece. Secondary result might come in form of row chaining between database blocks which will introduce additional single block reads to walk your rows.

Now let's have a little test. I've created two tables where all columns are VARCHAR2 with value 'X' except first and last column which is NUMBER with value 1. First table has 250 columns and second has 300.

I've traced four following SQL statements:

SELECT SUM(c1) from table_250;
SELECT SUM(c250) from table_250;
SELECT SUM(c1) from table_300;
SELECT SUM(c300) from table_300;

Let's check important parts of trace files:



As you can see, sum of column c300 is quite CPU heavy. There are also other consequences of row chaining with connection to table full scan and buffer cache ... which is not as good as you can guess by now.

I would encourage you to check blog post by Jonathan Lewis which covers buffer cache and other important things.

So remember folks ... one does not simply create 300 columns table. You have to have very good reason for that and you have to think about order of columns and consequences.