Flashback query and cursor: pin S wait on X


03.03.2023
by Kamil Stawiarski

Check out those charts:

During my last performance tuning audit I found out that database is consuming big amounts of CPU on parsing. One of the queries that took significant amount of time was rather a small DELETE looking something like this:

delete hr.tab_sessions where id=:B1;

Funny story tho – the table is usually empty but during the hot period, session could wait on that small delete even a few seconds.

It was pretty obvious that the problem was not with a DELETE itself, but there had to be something more. The problem was associated with a set of triggers and the triggers were producing queries with AS OF SCN clause.

The problem is that since database version 11.2.0.4 flashback queries have a very strange behavior – according to MOS Using the Flashback Query Feature Results in a High Number of Child Cursors (Doc ID 2192862.1)

This behavior is expected and by design and confirmed by Oracle Development that flashback query cannot share child cursors.   However, this was not enforced until 11.2.0.4 when a fix was introduced to prevent an error condition that could be caused by the sharing.

Let me visualize why this is a problem with a simple query looking like this:

select /*TEST1*/ count(1) from hr.employees as of timestamp systimestamp - interval '10' minute

To understand what is going on, we will use to following eBPF script:

BEGIN {
        @cnt = 0;
        @sum = 0;
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kkscsCheckCursor+2 /pid == $1/ {
        printf("\t\tChecking cursor, %d\n", @cnt);
        @cnt++;
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kkscsSearchChildList+2 /pid == $1/ {
        printf("\tSearching Child List of a cursor\n");
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:qksctxAddPlanHashValue+2 /pid == $1/ {
        printf("\t\t\tAdd Hash value for a plan\n");
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kglHandleInitialize+2 /pid == $1/ {
        printf("\t\t\tInitialize cursor handle\n");
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kksParseCursor+2 /pid == $1/ {
        @sum += @cnt;
        @cnt = 0;
        printf("Parsing cursor: %s\n", str(arg1));
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kksLoadChild+2 /pid == $1/ {
        if (arg4 !=0) {
                printf("\t\tLoading new incomplete child at address %x\n", arg4);
        } else {
                printf("\t\tFinnishing child load\n");
        }
}
END {
  printf("Checked %d cursors\n", @cnt);
}

Each time I execute the above query, my simple script shows the following output:

[root@dbVictim ~]# bpftrace kkscsCheckCursor.bpftrace 365150
Attaching 8 probes...
Parsing cursor: select /*TEST1*/ count(1) from hr.employees as of timestamp sys
	Searching Child List of a cursor
		Checking cursor, 0
		Checking cursor, 1
		Checking cursor, 2
		Checking cursor, 3
		Checking cursor, 4
		Checking cursor, 5
		Loading new incomplete child at address 8f6f7588
			Initialize cursor handle
			Initialize cursor handle
		Finnishing child load
			Initialize cursor handle
			Add Hash value for a plan
Parsing cursor: select max(time_mp) from smon_scn_time
Parsing cursor: select time_mp, scn, num_mappings, tim_scn_map    from smon_scn
	Searching Child List of a cursor
		Checking cursor, 0
Parsing cursor: select /*TEST1*/ count(1) from hr.employees as of timestamp sys
	Searching Child List of a cursor
		Checking cursor, 0
		Checking cursor, 1
		Checking cursor, 2
		Checking cursor, 3
		Checking cursor, 4
		Checking cursor, 5
		Checking cursor, 6
		Loading new incomplete child at address 934b8310
			Initialize cursor handle
			Initialize cursor handle
		Finnishing child load
			Initialize cursor handle
			Add Hash value for a plan
Parsing cursor: select max(time_mp) from smon_scn_time
Parsing cursor: select /*TEST1*/ count(1) from hr.employees as of timestamp sys
	Searching Child List of a cursor
		Checking cursor, 0
		Checking cursor, 1
		Checking cursor, 2
		Checking cursor, 3
		Checking cursor, 4
		Checking cursor, 5
		Checking cursor, 6
		Checking cursor, 7
		Loading new incomplete child at address 919a04c0
			Initialize cursor handle
			Initialize cursor handle
		Finnishing child load
			Initialize cursor handle
			Add Hash value for a plan
Parsing cursor: select max(time_mp) from smon_scn_time

It means that each time you execute a flashback query, the function kkscsSearchChildList has to call kkscsCheckCursor for each child cursor that exists in the Library Cache. Since a flashback query can’t share child cursors, at the end of the child list a kksLoadChild has to be executed to initialize a new child and associate an execution plan with this new child.

Loading a new child requires an exclusive mutex. We can modify our eBPF trace script to show the amount of exclusive mutexes:

BEGIN {
	@cnt = 0;
	@sum = 0;
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kkscsCheckCursor+2 /pid == $1/ {
        printf("\t\tChecking cursor, %d\n", @cnt);
        @cnt++;
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kkscsSearchChildList+2 /pid == $1/ {
	printf("\tSearching Child List of a cursor\n");
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:qksctxAddPlanHashValue+2 /pid == $1/ {
	printf("\t\t\tAdd Hash value for a plan\n");
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kglHandleInitialize+2 /pid == $1/ {
	printf("\t\t\tInitialize cursor handle\n");
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kgxExclusive+2 /pid == $1/ {
	printf("\t\t\t\tSetitng exclusive mutex: %x\n", arg1);
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kglReleaseMutex+2 /pid == $1/ {
	printf("\t\t\t\tRelease mutex\t\t:%x\n", arg1);
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kksParseCursor+2 /pid == $1/ {
	@sum += @cnt;
	@cnt = 0;
	printf("Parsing cursor: %s\n", str(arg1));
}
uprobe:/opt/oracle/product/19c/dbhome_1/bin/oracle:kksLoadChild+2 /pid == $1/ {
	if (arg4 !=0) {
		printf("\t\tLoading new incomplete child at address %x\n", arg4);
	} else {
		printf("\t\tFinnishing child load\n");
	}
}
END {
  printf("Checked %d cursors\n", @cnt);
}

And now we can understand a bit more about complexity of work that is going on under the hood:

[root@dbVictim ~]# bpftrace kkscsCheckCursor.bpftrace 365150
Attaching 10 probes...
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
Parsing cursor: select /*TEST1*/ count(1) from hr.employees as of timestamp sys
				Setitng exclusive mutex: a3f391c8
				Release mutex		:a3f391c8
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:a3f391c8
				Release mutex		:9ee5d650
				Release mutex		:9ee5d650
				Release mutex		:9ee5d650
	Searching Child List of a cursor
		Checking cursor, 0
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
				Release mutex		:9ee5d650
				Setitng exclusive mutex: 64cf6d08
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
		Checking cursor, 1
		Checking cursor, 2
		Checking cursor, 3
		Checking cursor, 4
		Checking cursor, 5
		Checking cursor, 6
		Checking cursor, 7
		Checking cursor, 8
		Checking cursor, 9
		Checking cursor, 10
		Checking cursor, 11
		Checking cursor, 12
		Checking cursor, 13
		Checking cursor, 14
		Checking cursor, 15
		Checking cursor, 16
		Checking cursor, 17
		Checking cursor, 18
		Checking cursor, 19
		Checking cursor, 20
		Checking cursor, 21
		Checking cursor, 22
		Checking cursor, 23
		Checking cursor, 24
		Checking cursor, 25
		Checking cursor, 26
		Checking cursor, 27
		Checking cursor, 28
		Checking cursor, 29
		Checking cursor, 30
		Checking cursor, 31
		Checking cursor, 32
		Checking cursor, 33
		Checking cursor, 34
		Checking cursor, 35
		Checking cursor, 36
		Checking cursor, 37
		Checking cursor, 38
		Checking cursor, 39
		Checking cursor, 40
		Checking cursor, 41
		Checking cursor, 42
		Checking cursor, 43
		Checking cursor, 44
		Checking cursor, 45
		Checking cursor, 46
		Checking cursor, 47
		Checking cursor, 48
		Checking cursor, 49
		Checking cursor, 50
		Checking cursor, 51
		Checking cursor, 52
		Checking cursor, 53
		Checking cursor, 54
		Checking cursor, 55
		Checking cursor, 56
				Setitng exclusive mutex: a2499a40
				Release mutex		:a2499a40
				Setitng exclusive mutex: 9b52ed98
				Release mutex		:a2499a40
				Release mutex		:9b52ed98
				Release mutex		:9b52ed98
				Setitng exclusive mutex: 9b52ed98
				Release mutex		:9b52ed98
				Setitng exclusive mutex: 64cf6d08
				Setitng exclusive mutex: 64cf7108
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
				Release mutex		:9ee5d650
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
				Setitng exclusive mutex: a23eb3f0
				Setitng exclusive mutex: a07750b8
				Release mutex		:a23eb3f0
				Release mutex		:a07750b8
				Release mutex		:a07750b8
				Release mutex		:a07750b8
				Setitng exclusive mutex: a220a068
				Setitng exclusive mutex: 9bde46c0
				Release mutex		:a220a068
				Release mutex		:9bde46c0
				Release mutex		:9bde46c0
				Release mutex		:9bde46c0
		Loading new incomplete child at address 6f613d68
				Setitng exclusive mutex: 64cf6d08
				Setitng exclusive mutex: a21ca558
				Setitng exclusive mutex: 84cf8658
				Release mutex		:a21ca558
				Release mutex		:84cf8658
				Release mutex		:84cf8658
				Release mutex		:84cf8658
				Setitng exclusive mutex: 84cf8658
				Release mutex		:84cf8658
				Setitng exclusive mutex: 84cf8658
				Release mutex		:84cf8658
			Initialize cursor handle
				Setitng exclusive mutex: 84cf8658
				Release mutex		:84cf8658
				Setitng exclusive mutex: 84cf8658
				Release mutex		:84cf8658
				Release mutex		:84cf8658
				Setitng exclusive mutex: 84cf8658
				Setitng exclusive mutex: 84cf85b8
				Release mutex		:84cf85b8
				Release mutex		:84cf8658
				Setitng exclusive mutex: 9ee5d650
				Setitng exclusive mutex: 9ee5d5b0
				Release mutex		:9ee5d5b0
				Release mutex		:9ee5d650
				Setitng exclusive mutex: 84cf8658
				Release mutex		:84cf8658
				Setitng exclusive mutex: 84cf8658
				Release mutex		:84cf8658
			Initialize cursor handle
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
				Setitng exclusive mutex: 9bde46c0
				Release mutex		:9bde46c0
				Setitng exclusive mutex: a07750b8
				Release mutex		:a07750b8
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
				Release mutex		:9ee5d650
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
		Finnishing child load
				Setitng exclusive mutex: 64cf6d08
			Initialize cursor handle
				Setitng exclusive mutex: 9ee5d650
				Release mutex		:9ee5d650
				Setitng exclusive mutex: a23eb3f0
				Setitng exclusive mutex: a07750b8
				Release mutex		:a23eb3f0
				Release mutex		:a07750b8
				Release mutex		:a07750b8
				Release mutex		:a07750b8
				Setitng exclusive mutex: a220a068
				Setitng exclusive mutex: 9bde46c0
				Release mutex		:a220a068
				Release mutex		:9bde46c0
				Release mutex		:9bde46c0
				Release mutex		:9bde46c0
				Setitng exclusive mutex: 64cf6d08
				Setitng exclusive mutex: 64cf6d08
				Setitng exclusive mutex: a2455bd8
				Release mutex		:a2455bd8
				Setitng exclusive mutex: 9675db10
				Release mutex		:a2455bd8
				Release mutex		:9675db10
				Release mutex		:9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: 9675db10
				Setitng exclusive mutex: 9675da70
				Release mutex		:9675da70
				Release mutex		:9675db10
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: a46878f8
				Setitng exclusive mutex: 96da8380
				Setitng exclusive mutex: 96da8380
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a478f870
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: a478f870
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: a4ce25b0
				Release mutex		:a4ce25b0
				Setitng exclusive mutex: 9dae8590
				Release mutex		:a4ce25b0
				Release mutex		:9dae8590
				Release mutex		:9dae8590
				Release mutex		:9dae8590
				Setitng exclusive mutex: 9dae8590
				Release mutex		:9dae8590
				Setitng exclusive mutex: 9dae8590
				Release mutex		:9dae8590
				Release mutex		:9dae8590
				Setitng exclusive mutex: 9dae8590
				Release mutex		:9dae8590
				Setitng exclusive mutex: 9dae8590
				Release mutex		:9dae8590
				Setitng exclusive mutex: a48e28e8
				Setitng exclusive mutex: 9b68ea08
				Setitng exclusive mutex: 9b68ea08
				Setitng exclusive mutex: a489e710
				Setitng exclusive mutex: 9de12258
				Setitng exclusive mutex: 9de12258
				Setitng exclusive mutex: a48bcb30
				Setitng exclusive mutex: 9b17b278
				Setitng exclusive mutex: 9b17b278
				Setitng exclusive mutex: a491d208
				Setitng exclusive mutex: 8a9d89a8
				Setitng exclusive mutex: 8a9d89a8
				Setitng exclusive mutex: a491d220
				Setitng exclusive mutex: 9b127980
				Setitng exclusive mutex: 9b127980
				Setitng exclusive mutex: a4945240
				Setitng exclusive mutex: 843bf2b0
				Setitng exclusive mutex: 843bf2b0
				Setitng exclusive mutex: a48e28e8
				Setitng exclusive mutex: 9b68ea08
				Setitng exclusive mutex: 9b68ea08
				Setitng exclusive mutex: a48a0f78
				Setitng exclusive mutex: 734abeb8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 734abeb8
				Setitng exclusive mutex: a48a0f78
				Setitng exclusive mutex: 734abeb8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 734abeb8
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: a48e28e8
				Setitng exclusive mutex: 9b68ea08
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 9b68ea08
			Add Hash value for a plan
				Setitng exclusive mutex: a48a0f78
				Setitng exclusive mutex: 734abeb8
				Setitng exclusive mutex: a4750eb8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 8cde26f8
				Setitng exclusive mutex: 734abeb8
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: 9675db10
				Release mutex		:9675db10
				Setitng exclusive mutex: 9bde46c0
				Release mutex		:9bde46c0
				Setitng exclusive mutex: a07750b8
				Release mutex		:a07750b8
				Setitng exclusive mutex: 64cf6d08
				Setitng exclusive mutex: 64cf6d08
				Setitng exclusive mutex: 64cf7108
				Setitng exclusive mutex: 64cf6e08
				Setitng exclusive mutex: 9b52ed98
				Release mutex		:9b52ed98
				Setitng exclusive mutex: 64cf6d08
Parsing cursor: select max(time_mp) from smon_scn_time

Something like this happens each time the query is being executed. When the amount of sessions executing the flashback query rises, we can hit some serious contention in the Concurrency wait event class.

Let’s simulate this behavior by setting a breakpoint on kksLoadChild function:

(gdb) b kksLoadChild
Breakpoint 1 at 0x321d330
(gdb) c
Continuing.

Breakpoint 1, 0x000000000321d330 in kksLoadChild ()

After this first hit, we know that the whole child list has been scanned and the new cursor handle is being initialized.

The session that tries to execute the same query will be waiting on kksfbc child completion until the child has been prepared. When we hit the second breakpoint in kksLoadChild the session will change wait event to cursor: pin S wait on X because the newest child is also not suitable for sharing and now this session wants to create a new child (exclusive mutex has to be set to do that operation), but the previous session didn’t release the mutex yet.

If you understand the complexity of the job now, imagine that you have 10 such cursors using flashback query mechanism, being executed houndred thousand of times and each having 30 000 child cursors in Library Cache, summing up to ~1GB of Sharable Memory for each cursor.

No wonder that this instance in burning CPU on parsing and waiting in Concurrency wait class.

If you are wondering if having a table in a flashback archive changes anything – it doesn’t.

So the conclusion is simple – do not use the flashback queries in your application logic if you know that those SQLs will be heavily executed by hundreds of concurrent sessions.


Contact us

Database Whisperers sp. z o. o. sp. k.
al. Jerozolimskie 200, 3rd floor, room 342
02-486 Warszawa
NIP: 5272744987
REGON:362524978
+48 508 943 051
+48 661 966 009
info@ora-600.pl

Newsletter Sign up to be updated