Oracle Scratchpad

December 28, 2009

Short Sorts

Filed under: Infrastructure,Performance,sorting,trace files,Tuning — Jonathan Lewis @ 7:29 pm BST Dec 28,2009

I posted a little holiday quiz – timed to appear just before midnight (GMT) on 24th December – that asked about the number of rows sorted and the memory used for queries like:

select sortcode
from
        (
  select sortcode
  from t1
        order by
  sortcode
        )
where
  rownum <= 10
;

The number and variety of the responses was gratifying. It’s always interesting to see how many important little details appear as people start to tackle even fairly straight-forward questions like this.

The first question fired back at me was the obvious one – what was the type definition of sortcode ? Without this critical piece of information it’s not possible to say much about the memory requirements. The answer was char(6).  (I hadn’t omitted this deliberately, it just happens that in the UK banking system sort-codes are 6 character fixed length codes – and that’s what the column was supposed to represent – not that anyone else would have known that, of course.)

But knowing that it’s char(6) isn’t enough – after all we have characters sets that are fixed single-byte at one extreme and variable length up to four bytes at the other.  On top of that, if you want to estimate the total memory used you also need to know whether you are talking about 32-bit Oracle or 64-bit Oracle because the size of the pointers used in the sort mechanism varies with the size of CPU. Then there’s the problem of Oracle version – since 10gR2 introduced a new sorting mechanism which (if it gets used) consumes less overhead.

One respondent even suggested that I might have set the table up as an index-organized table (IOT), or as a table in an index cluster - thus allowing myself an indexed access path without explicitly “creating an index ont the table”.  (A reasonable suggestion in a real-life scenario – but I would (probably) have given a little warning if I were trying to be tricky).  A suggestion that didn’t appear, but may have been relevant, was the possibility that the table was range partitioned on the column I was querying – it’s possible (but I haven’t checked it ) that even in the absence of indexes the optimizer might have done something  clever in some  versions to use partition elimination.  [Update: I see Charles Hooper thought of, and tested, that idea while I was typing up this note].

Various people then modelled the problem, using explain plan, event 10046 with tkprof, queries against v$sessstat and so on, so get an idea of what happens – and found some (apparently) contradictory results. In one case, checking v$sesstat showed 1,000,000 rows sorted, while a simultaneous check of the tkprof output reported the actual number of rows from the sort order by stopkey operation was 10.  In another case the execution plan from explain plan for this query and the simpler “select sortcode from t1 order by sortcode” both showed the same results for rows, bytes and temporary (sort) space.

The one thing that didn’t get mentioned in any of the comments was event 10032 - the event to trace sort statistics. Running 10.2.0.3, this is the content of the tracefile I got for the query returning just 10 rows (the table held 1,048,576 rows rather than the 1,000,000 stated in the original problem):


---- Sort Parameters ------------------------------
sort_area_size                    65536
sort_area_retained_size           65536
sort_multiblock_read_count        1
max intermediate merge width      3
---- Sort Statistics ------------------------------
Input records                             1048576
Output records                            114
Total number of comparisons performed     1048968
  Comparisons performed by in-memory sort 1048968
Total amount of memory used               8192
Uses version 1 sort
Does not use asynchronous IO
---- End of Sort Statistics -----------------------

You’ll notice that (a) there are no statistics about disk I/O – this sort completed in memory; (b) the memory allocated as a workarea by the end of the sort was 65,536  butes – I was using automatic workarea_size_policy, so the sort_area_size and sort_area_retained_size lines report the final memory allocation); and (c) the total memory used was only 8192 bytes.

It is a little odd, you may think, that the number of output rows was reported as 114 rather than the 10 we asked for; it’s also fairly important that Oracle has reported a version 1 sort - the mechanism that was replaced in 10gR2 by a new sorting algorithm.

In comparison look at the trace information for the query “select sortcode from t1 order by sortcode”


--- Sort Parameters ------------------------------
sort_area_size                    19013632
sort_area_retained_size           19013632
sort_multiblock_read_count        1
max intermediate merge width      1159
*** 2009-12-23 14:41:30.062
---- Sort Statistics ------------------------------
Input records                             1048576
Output records                            1048576
Total number of comparisons performed     11324792
  Comparisons performed by in-memory sort 11324792
Total amount of memory used               19013632
Uses version 2 sort
Does not use asynchronous IO

Note particularly that the memory demand is much higher, the sort is a version 2 sort, and the number of comparisons performed is much higher. Oracle is doing something a little bit clever when is handles the “short” sort, but it can only do it by using the version 1 sort.

I pointed out in Cost Based Oracle  – Fundamentals that Oracle does not “sort” data in the way that a person would sort a deck of cards (say); basic sorting algorithms for computers involve copying the data into memory and then manipulating a set of pointers to the data so that walking through the pointers in order causes you to jump around the coped data, reading it back in sorted order.

For a version 1 sort, Oracle’s algorithm uses a “binary insertion tree”. This means it is basically building a balanced b-tree index on the data as the data is read into memory; moreover there are only two entries in each node of the tree (each node points to just two child nodes or, ultimately, just two items of data). This balanced binary tree algorithm is resource-intensive on memory and CPU – which is why Oracle finally introduced a better algorithm in 10gR2. (When I forced Oracle to use the version 1 sort on the full data set the memory required was 24MB with 20M comparisons compared to the 19MB and 11M comparisons above).

However, if you are building a binary index as you read the data an interesting advantage appears when you only want to report a tiny subset of the full sorted data set. And at this point, you uncover the ambiguity in the question: “How many rows does Oracle sort?”

Consider the sequence of steps, which must go something like this:

  • we wish to report the top 10 rows, and the optimizer is aware of this and has special code to handle the task.
  • we copy the first 10 rows from the table into memory and build the binary tree – and we keep track of the largest value we have linked into the binary tree.
  • from this point onwards we read a value into memory and compare it with the known largest value in the tree
  • if the value is too large we discard it and read the next value, otherwise we insert it into the binary tree and discard the previous largest value from the tree

The total number of comparisons we perform is made up of two components – comparing every row with the current largest (so N-1 comparisons if we have N rows), and doing the comparisons (log2(N) for each row) to insert a few “replacement” values into a “small” binary tree.

So how many rows have we sorted ? You could argue for three possibilities -

  • the 10 rows we finally get
  • the 1,000,000 rows we looked at
  • or the “X” rows that we inserted into the binary tree as we went along

Notice in our “short sort” trace above that the number of output rows reports was 114 – I assume that this is the number of rows that were inserted into the binary tree. Since we have a binary tree holding 10 items it has a height of 4, so we should expect the total number of comparisons to be roughly 1048576 + 4 * 114 … and the actual result is pretty close.

As a final comment on how unfair my original question was, just think about the effect the order of the incoming data has on the efficiency of the algorithm. The number of inserts into the b-tree could vary dramatically with the pre-existing ordering of the data.  Here’s an extreme data set to make the point.

execute dbms_random.seed(0)

create table t1
as
with generator as (
	select	--+ materialize
rownum id
	from	all_objects
where rownum <= 5000
)
select
rownum - 1 ordered,
1000000 - rownum reversed,
trunc(dbms_random.value(1,1000000)) randomised
from
generator v1,
generator v2
where
rownum <= 1 * 1048576
;

select * from (
select * from t1 order by ordered
-- select * from t1 order by reversed
-- select * from t1 order by randomised
)
where
rownum <= 10
;

Depending on your choice of column in the order by clause, the output rows, comparisons, and memory used are as follows:

Ordered
-------
Output records                            10
Total number of comparisons performed     1048575
Total amount of memory used               8192

Reversed
--------
Output records                            1048576
Total number of comparisons performed     5242856
Total amount of memory used               1130496

Randomised
----------
Output records                            135
Total number of comparisons performed     1049032
Total amount of memory used               8192

11 Comments »

  1. Hmm…
    So the statement The table has a column called sortcode which has no nulls, and has been generated in a highly random way so that no value appears more than four times was just a smoke ?
    But thanks. I was not even aware of 10032 event and reading other comments made me realise what other possibilities I did not even think of.

    Comment by Narendra — December 28, 2009 @ 10:51 pm BST Dec 28,2009 | Reply

    • Narenda,

      No smoke, at least not intentionally. If you think about the two comments I made about the data, one of them tells you what it looks like, the other tells you what it is supposed to represent.

      Comment by Jonathan Lewis — December 30, 2009 @ 4:27 pm BST Dec 30,2009 | Reply

  2. I ran it on 11.2.0.1.0

    ordered
    —- Sort Statistics ——————————
    Input records 1048576
    Output records 10
    Total number of comparisons performed 1048575
    Comparisons performed by in-memory sort 1048575
    Total amount of memory used 2048
    Uses version 1 sort

    reversed
    —- Sort Statistics ——————————
    Input records 1048576
    Output records 1048576
    Total number of comparisons performed 5242856
    Comparisons performed by in-memory sort 5242856
    Total amount of memory used 1337344
    Uses version 1 sort

    randomised
    —- Sort Statistics ——————————
    Input records 1048576
    Output records 135
    Total number of comparisons performed 1049022
    Comparisons performed by in-memory sort 1049022
    Total amount of memory used 2048
    Uses version 1 sort

    All of them are using Version 1 sort. How can I force Oracle to use Version 2 sort?
    and how can I force it to use version 1 sort?

    Comment by Winston — December 29, 2009 @ 12:02 am BST Dec 29,2009 | Reply

  3. I don’t think your original question “How many rows does Oracle sort?” was unfair and ambiguous.

    But I think that here you are mixing up a bit of ideas.

    The first is the number of rows that there is in the table. It’s 1.000.000 full stop. So Oracle sort 1.000.000 rows, i.e. Oracle reads 1.000.000 rows and “do-some-magic” so we get them ordered.

    The second idea is how many comparisons Oracle does to sort that rows. “Comparisons” are a complete different kind of objects and they depends on the algorithm that is used to sort (i.e. qsort is better that bubble sort because it does less comparisons).

    Reading your post, I get the idea that you mean “Oracle have 1.000.000 rows but it sorts 2.000.000 rows” (as it multiplies it), well this is wrong, at least “we can say that Oracle do 3.000.000 comparisons to sort 1.000.000″ but the number of rows doesn’t change.

    Bye,
    Antonio

    Comment by lascoltodelvenerdi — December 30, 2009 @ 7:58 am BST Dec 30,2009 | Reply

    • Antonio,

      I’ve re-read the posting and don’t see any confusion between sorting and comparing. What I see is that we can infer from the change in the number of comparisons that something different has happened during sorting.

      One of your commnet, however, highlights the ambiguity that surrounds the word “sorting” in this context.

      When as ask for 10 rows, the mechanism does not “sort” 1,000,000 rows because it does NOT get 1,000,000 rows into order; it gets 10 rows into order, and 999,990 rows which are not well-ordered.

      Comment by Jonathan Lewis — December 30, 2009 @ 4:35 pm BST Dec 30,2009 | Reply

  4. Shall we understand that, given the nature of the 10gr2 binary tree sorting, it is not possible to respond in advance to the Quiz unless an exact measure of the disorganization of the table is also provided for sorting load is now related to clustering factor.

    Comment by Bernard Polarski — December 30, 2009 @ 8:32 am BST Dec 30,2009 | Reply

    • Bernard,

      I wouldn’t have used the expression “clustering factor” in this context (data can be well clustered without being well-ordered) but you are essentially correct.

      For this type of “short sort”, the work done and the memory used do depend on the arrival order of the data.

      (Footnote: My memory from the days when I did computer science is that every sort algorithm was subject to some boundary cases where the data order was “bad for” the algorithm – basically the order of input always affected the amount of work needed and the worst case was always very bad.)

      Comment by Jonathan Lewis — December 30, 2009 @ 4:47 pm BST Dec 30,2009 | Reply

  5. [...] 5-How many rows are sorted and how much memory used by your query? Jonathan Lewis-Short Sorts [...]

    Pingback by Blogroll Report 25/12/2009 – 01/01/2010 « Coskan’s Approach to Oracle — January 8, 2010 @ 6:50 pm BST Jan 8,2010 | Reply

  6. [...] for a sort aggregate operation can be very small compared to the volume of data processed. (See this note for an example of how Oracle can handle one type of special case involving [...]

    Pingback by Count(*) « Oracle Scratchpad — March 6, 2012 @ 4:58 pm BST Mar 6,2012 | Reply

  7. […] this operation (like SORT ORDER BY STOPKEY) is able to limit the data volume sorted (See http://jonathanlewis.wordpress.com/2009/12/28/short-sorts/ ), but does the older, less efficient, “Version 1″ sort to achieve this. If you […]

    Pingback by 12c First N | Oracle Scratchpad — October 28, 2013 @ 12:46 pm BST Oct 28,2013 | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Rubric Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 3,530 other followers