Why does my query run in parallel on Autonomous Database-Serverless even though I do everything to prevent it from doing so?

While preparing for another blog post I desperately wanted to write I noticed something unexpected. The problem turned out to be between keyboard and chair, but I don’t want to get ahead of myself.

Tell me what happened already!

Using Oracle SQL Developer Extension for Visual Studio Code, connecting to an Autonomous Database-Serverless, a query against my Swingbench Order Entry (SOE) schema ran in parallel. Which is odd, OrderEntry is an OLTP style schema/benchmark, and parallelism is most definitely unwanted in these…. I’ll take you along on a non-AI aided, self-written investigation into the problem and how I eventually solved it.

The solution is rather simple, but still worth knowing. Hopefully this post saves you 5 minutes scratching your head!

Background

I cloned an existing connection in SQLDev for VSCode, changed username and password and connected against the database. So far, so good. The particular query, shown here with its execution plan, runs in parallel as you can see with all these PX steps and the PQ Distribution column in the execution plan:

SQL> select
  2      *
  3  from
  4*     table(dbms_xplan.display_cursor(sql_id => '8hyqhnjzg5kv8', cursor_child_no => 1))
SQL> /

PLAN_TABLE_OUTPUT                                                                                                                                          
__________________________________________________________________________________________________________________________________________________________ 
SQL_ID  8hyqhnjzg5kv8, child number 1                                                                                                                      
-------------------------------------                                                                                                                      
select /*+ gather_plan_statistics monitor */     c.customer_id,                                                                                            
c.cust_first_name     || ' '     || c.cust_last_name as customer_name,                                                                                     
   o.order_id,     o.order_date,     o.order_total,                                                                                                        
oi.line_item_id,     p.product_name,     oi.quantity,                                                                                                      
oi.unit_price,     ( oi.quantity * oi.unit_price ) as line_total from                                                                                      
  customers c     inner join orders o on c.customer_id = o.customer_id                                                                                     
   inner join order_items oi on o.order_id = oi.order_id     inner join                                                                                    
product_information p on oi.product_id = p.product_id where                                                                                                
c.account_mgr_id = 146 order by     o.order_date desc,     c.customer_id                                                                                   
                                                                                                                                                           
Plan hash value: 4155957470                                                                                                                                
                                                                                                                                                           
-------------------------------------------------------------------------------------------------------------------------------------------------------    
| Id  | Operation                                      | Name                    | Rows  | Bytes | Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |    
-------------------------------------------------------------------------------------------------------------------------------------------------------    
|   0 | SELECT STATEMENT                               |                         |       |       |  1322 (100)|          |        |      |            |    
|   1 |  PX COORDINATOR                                |                         |       |       |            |          |        |      |            |    
|   2 |   PX SEND QC (ORDER)                           | :TQ10004                |  1866 |   180K|  1322   (1)| 00:00:01 |  Q1,04 | P->S | QC (ORDER) |    
|   3 |    SORT ORDER BY                               |                         |  1866 |   180K|  1322   (1)| 00:00:01 |  Q1,04 | PCWP |            |    
|   4 |     PX RECEIVE                                 |                         |  1866 |   180K|  1321   (0)| 00:00:01 |  Q1,04 | PCWP |            |    
|   5 |      PX SEND RANGE                             | :TQ10003                |  1866 |   180K|  1321   (0)| 00:00:01 |  Q1,03 | P->P | RANGE      |    
|*  6 |       HASH JOIN BUFFERED                       |                         |  1866 |   180K|  1321   (0)| 00:00:01 |  Q1,03 | PCWP |            |    
|   7 |        TABLE ACCESS FULL                       | PRODUCT_INFORMATION     |  1000 | 27000 |     5   (0)| 00:00:01 |  Q1,03 | PCWP |            |    
|   8 |        NESTED LOOPS                            |                         |  1879 |   132K|  1316   (0)| 00:00:01 |  Q1,03 | PCWP |            |    
|   9 |         NESTED LOOPS                           |                         |  1879 |   132K|  1316   (0)| 00:00:01 |  Q1,03 | PCWP |            |    
|* 10 |          HASH JOIN                             |                         |   366 | 19032 |   503   (0)| 00:00:01 |  Q1,03 | PCWP |            |    
|  11 |           PX RECEIVE                           |                         |       |       |            |          |  Q1,03 | PCWP |            |    
|  12 |            PX SEND HASH                        | :TQ10001                |       |       |            |          |  Q1,01 | P->P | HASH       |    
|  13 |             TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS               |   194 |  4850 |   120   (0)| 00:00:01 |  Q1,01 | PCWP |            |    
|  14 |              BUFFER SORT                       |                         |       |       |            |          |  Q1,01 | PCWC |            |    
|  15 |               PX RECEIVE                       |                         |   124 |       |     3   (0)| 00:00:01 |  Q1,01 | PCWP |            |    
|  16 |                PX SEND HASH (BLOCK ADDRESS)    | :TQ10000                |   124 |       |     3   (0)| 00:00:01 |  Q1,00 | S->P | HASH (BLOCK|    
|  17 |                 PX SELECTOR                    |                         |       |       |            |          |  Q1,00 | SCWC |            |    
|* 18 |                  INDEX RANGE SCAN              | CUST_ACCOUNT_MANAGER_IX |   124 |       |     3   (0)| 00:00:01 |  Q1,00 | SCWP |            |    
|  19 |           PX RECEIVE                           |                         |     2 |    54 |     2   (0)| 00:00:01 |  Q1,03 | PCWP |            |    
|  20 |            PX SEND HASH                        | :TQ10002                |     2 |    54 |     2   (0)| 00:00:01 |  Q1,02 | P->P | HASH       |    
|  21 |             PX BLOCK ITERATOR                  |                         |     2 |    54 |     2   (0)| 00:00:01 |  Q1,02 | PCWC |            |    
|* 22 |              TABLE ACCESS FULL                 | ORDERS                  |     2 |    54 |     2   (0)| 00:00:01 |  Q1,02 | PCWP |            |    
|* 23 |          INDEX RANGE SCAN                      | ITEM_ORDER_IX           |     5 |       |     1   (0)| 00:00:01 |  Q1,03 | PCWP |            |    
|  24 |         TABLE ACCESS BY INDEX ROWID            | ORDER_ITEMS             |     5 |   100 |     2   (0)| 00:00:01 |  Q1,03 | PCWP |            |    
-------------------------------------------------------------------------------------------------------------------------------------------------------    
                                                                                                                                                           
Predicate Information (identified by operation id):                                                                                                        
---------------------------------------------------                                                                                                        
                                                                                                                                                           
   6 - access("OI"."PRODUCT_ID"="P"."PRODUCT_ID")                                                                                                          
  10 - access("C"."CUSTOMER_ID"="O"."CUSTOMER_ID")                                                                                                         
  18 - access("C"."ACCOUNT_MGR_ID"=146)                                                                                                                    
  22 - access(:Z>=:Z AND :Z<=:Z)                                                                                                                           
  23 - access("O"."ORDER_ID"="OI"."ORDER_ID")                                                                                                              
                                                                                                                                                           
Note                                                                                                                                                       
-----                                                                                                                                                      
   - dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))                                                                                       
   - automatic DOP: Computed Degree of Parallelism is 2                                                                                                    
   - this is an adaptive plan                                                                                                                              
                                                                                                                                                           

59 rows selected. 

Right, ok, time to enter troubleshooting mode.

Troubleshooting

Often, when queries start running in parallel, one of the segments touched has a degree of parallelism (DOP) set to <> 1. This sounds like a logical cause for the parallel execution; I previously ran sbutil (part of the Swingbench toolchest), inflating the benchmark data volume by a factor of two. The run aborted due to a network issue, and it left all the indexes with a DOP of 4. Easy to fix! Just alter index soe.theIndexName noparallel and you’re done.

Did that, then ran the query again … no change in behaviour 🤔. Right, ok, more thinking required.

This is a playground environment I use for writing blog posts, so a little blunt force is in order – let’s go ahead and purge the cursor from the shared pool: I want to force a fresh parse. Using the flush.sql script from my script repository, I got rid of the cursor:

SQL> SELECT
  2      child_number,
  3      plan_hash_value,
  4      invalidations,
  5      executions
  6  FROM
  7      v$sql
  8  WHERE
  9      sql_id = '8hyqhnjzg5kv8';

no rows selected

Still, no change, the query continued to run in parallel.

The solution

I could have passed a /*+ noparallel */ hint to the query (which solves my problem), but that requires additional TLC moving forwards. Hints are always tricky business, for reasons outlined by people way smarter than me. Suffice to say I didn’t want to use any.

I still had to understand why the query executes in parallel. After a little time it dawned on me: depending on how you connect to an Autonomous Database instance, different degrees of parallelism are defined, tied to the service name. And sure enough, I connected to one of the PX-enabled services: databaseName_high.

databaseName_high is one of the predefined service names in Autonomous Database, and that’s a pretty awesome concept. Here’s the table containing the details about PX and concurrency with predefined service names in Autonomous Database-Serverless. It might get out of date over time, so make sure you check the source.

As you can see, PX is enabled for the “high” service name, and that’s why my query executed in parallel.

With that information at hand, solving the mystery is a doddle. Change the connection information, and connect to the databaseName_TP service, or, as an alternative, databaseName_low. I went for blogpost_low, reconnected, and sure enough, things start to look like what I expected them to:

SQL> select /*+ gather_plan_statistics monitor */
  2      c.customer_id,
  3      c.cust_first_name
  4      || ' '
  5      || c.cust_last_name as customer_name,
  6      o.order_id,
  7      o.order_date,
  8      o.order_total,
  9      oi.line_item_id,
 10      p.product_name,
 11      oi.quantity,
 12      oi.unit_price,
 13      ( oi.quantity * oi.unit_price ) as line_total
 14  from
 15      customers c
 16      inner join orders o on c.customer_id = o.customer_id
 17      inner join order_items oi on o.order_id = oi.order_id
 18      inner join product_information p on oi.product_id = p.product_id
 19  where
 20      c.account_mgr_id = 146
 21  order by
 22      o.order_date desc,
 23*     c.customer_id;

   CUSTOMER_ID CUSTOMER_NAME             ORDER_ID ORDER_DATE                        ORDER_TOTAL    LINE_ITEM_ID PRODUCT_NAME                             QUANTITY    UNIT_PRICE    LINE_TOTAL 
______________ ______________________ ___________ ______________________________ ______________ _______________ _____________________________________ ___________ _____________ _____________ 
        253648 terry nguyen               2078576 15.04.12 09:40:16,384000000              4774               3 iPkH7g23hN2MFrYUeF8AHtXk6E2QJ C                 5          1070          5350 

...

SQL> select * from table(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT                                                                                                               
_______________________________________________________________________________________________________________________________ 
SQL_ID  8hyqhnjzg5kv8, child number 0                                                                                           
-------------------------------------                                                                                           
select /*+ gather_plan_statistics monitor */     c.customer_id,                                                                 
c.cust_first_name     || ' '     || c.cust_last_name as customer_name,                                                          
   o.order_id,     o.order_date,     o.order_total,                                                                             
oi.line_item_id,     p.product_name,     oi.quantity,                                                                           
oi.unit_price,     ( oi.quantity * oi.unit_price ) as line_total from                                                           
  customers c     inner join orders o on c.customer_id = o.customer_id                                                          
   inner join order_items oi on o.order_id = oi.order_id     inner join                                                         
product_information p on oi.product_id = p.product_id where                                                                     
c.account_mgr_id = 146 order by     o.order_date desc,     c.customer_id                                                        
                                                                                                                                
Plan hash value: 1043292108                                                                                                     
                                                                                                                                
----------------------------------------------------------------------------------------------------------------------------    
| Id  | Operation                                | Name                    | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |    
----------------------------------------------------------------------------------------------------------------------------    
|   0 | SELECT STATEMENT                         |                         |       |       |       | 44969 (100)|          |    
|   1 |  SORT ORDER BY                           |                         | 35297 |  3412K|  3776K| 44969   (1)| 00:00:02 |    
|*  2 |   HASH JOIN                              |                         | 35297 |  3412K|       | 44172   (1)| 00:00:02 |    
|   3 |    TABLE ACCESS FULL                     | PRODUCT_INFORMATION     |  1000 | 27000 |       |     9   (0)| 00:00:01 |    
|   4 |    NESTED LOOPS                          |                         | 35544 |  2499K|       | 44163   (1)| 00:00:02 |    
|   5 |     NESTED LOOPS                         |                         | 35544 |  2499K|       | 44163   (1)| 00:00:02 |    
|   6 |      NESTED LOOPS                        |                         |  6918 |   351K|       | 16484   (1)| 00:00:01 |    
|   7 |       TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS               |  3669 | 91725 |       |  3456   (1)| 00:00:01 |    
|*  8 |        INDEX RANGE SCAN                  | CUST_ACCOUNT_MANAGER_IX |  3669 |       |       |    11   (0)| 00:00:01 |    
|   9 |       TABLE ACCESS BY INDEX ROWID BATCHED| ORDERS                  |     2 |    54 |       |     4   (0)| 00:00:01 |    
|* 10 |        INDEX RANGE SCAN                  | ORD_CUSTOMER_IX         |     2 |       |       |     2   (0)| 00:00:01 |    
|* 11 |      INDEX RANGE SCAN                    | ITEM_ORDER_IX           |     5 |       |       |     2   (0)| 00:00:01 |    
|  12 |     TABLE ACCESS BY INDEX ROWID          | ORDER_ITEMS             |     5 |   100 |       |     4   (0)| 00:00:01 |    
----------------------------------------------------------------------------------------------------------------------------    
                                                                                                                                
Predicate Information (identified by operation id):                                                                             
---------------------------------------------------                                                                             
                                                                                                                                
   2 - access("OI"."PRODUCT_ID"="P"."PRODUCT_ID")                                                                               
   8 - access("C"."ACCOUNT_MGR_ID"=146)                                                                                         
  10 - access("C"."CUSTOMER_ID"="O"."CUSTOMER_ID")                                                                              
  11 - access("O"."ORDER_ID"="OI"."ORDER_ID")                                                                                   
                                                                                                                                
Note                                                                                                                            
-----                                                                                                                           
   - this is an adaptive plan                                                                                                   
                                                                                                                                

44 rows selected

That really is all there is to be said

Summary

Oracle Autonomous Database is very cleverly engineered, and it adheres to some of the industry’s best practices when it comes to workload management. I have previously tied resource consumer groups to services, and enabled/disabled PX along with other resource management limits suitable for consolidating large numbers of databases on Exadata. The nice thing is that I don’t have to do that with an Autonomous Database. Oracle did it for me, and they have been doing for quite some time.

The only thing you need to be aware of are the service names available to your users. Assign them according to their roles, and you won’t see surprises like I did.