Oracle performance programming: .NET
In Oracle SQL High Performance tuning, I included an appendix in which I outlined how to achieve good cursor management, bind variables and array processing in the major languages of the day. I had intended to do the same in Oracle Performance Survival Guide, but I ran out of time and space in the book. So the examples in the book are generally in Java or PL/SQL only.
I wanted to get up to date on the various languages, some of which (like Python) I haven't used for a while and others (Ruby for instance) I've never used with Oracle. So I thought I'd kill two birds with one stone by writing a series of blog posts on how to program efficiently in the various languages.
There's lots of best practices in each language, but I think most of us would agree that you at least need to know how to do the following:
- Use bind variables appropriately.
- Ensure you are performing array fetch when retrieving more than one row
- Ensure that you use array insert when doing bulk inserts
The ways of doing this are different in each language. In Chapter 6 of Oracle Performance Survival Guide, I describe these techniques and their performance implications, using Java and sometimes PL/SQL examples. In this series I'll go through the techniques for other languages.
Let's start with ODP.NET, which is Oracle's ADO.NET driver for .NET languages such as C#, VB.NET and Powershell (see here for a posting on using Oracle with powershell).
You can get the C# program which contains the code snippets below here.
Bind variables
As in most languages, it's all to easy to omit bind variables in C#. Here's an example where the variable value is simply concatenated into a SQL string. The values to be fetched are obtained from an array "xvalueList":
Every value of xvalue will generate a unique SQL which will have to be parsed (unless the database parameter CURSOR_SHARING is set to FORCE or SIMILAR).
To use bind variables, we need to make 3 changes to the code. The changes are shown in the code below:
- Firstly, we define the bind variable "xVal" in the text string that represents the SQL statement (line 231).
- Next, we create an OracleParameter object representing the bind variable and assign it a datatype (line 233). We need to do this for every bind variable we use.
- Finally, we assign the parameter value to the value of the program variable that we want to use (line 237).
As with any programming language, using bind variables improves performance by reducing parse overhead and library cache latch or mutex contention. The above SQL is about as simple as you can get, so parse overhead reductions should be modest. As the only active session on my (local laptop) database, library cache contention would not be expected either. Nevertheless, elapsed time even in this simple example reduced by about 2/3rds when bind variables were used.
Array Select
Fetching rows in batches generally reduces elapsed time for bulk selects by up to a factor of 10. Array fetch works to improve performance by reducing network round trips and by reducing logical reads.
Array fetch is turned on by default in ODP.NET. The size of the array is determined by the FetchSize property of the OracleCommand object. It can also be set in the OracleDataReader object. The value is specified as the number of bytes allocated to the array, not the size of the array. This makes it tricky to set an exact array fetch size unless you know the row length. However, you can get the row length once the statement is executed by examining the RowSize parameter.
The default array size is 64K which will generally be adequate for good performance. Below we explicity set the fetch size to the size specified by the variable myFetchSize:
You won't normally need to adjust FetchSize. Below you can see the effect of adjusting the FetchSize to 64 - the size of a single row for this table - and to 6400 - the size of 100 rows:
Array insert
Array insert has similar performance implications as array fetch, but it harder to program and does not happen automatically. Firstly, the rows to be inserted must exist within a .NET array variables. The following declares and populates an array of two integer variables that we are going to insert:
The array is bound to bind variables as usual. On lines 167-170 below we create the Oracle Parameter objects for the bind variables. and on lines 172-173 we assign the arrays to the bind variables. On line 176 we set the number of rows to be inserted in the array insert; we've set it to be equal to the number of values in the array.
We just need to perform one insert - on line 177.
Below we see the performance improvements between inserting in arrays and inserting row by row. In this case, the database was my local laptop so the network latency was truly minimal. Even bigger performance improvements can be observed when the database is across a slow network link:
Conclusion
There's lot's of advanced techniques for working with ADO.NET, but at the very least you should be familiar with bind variables and array processing. Of the three, only array fetch is implemented by default and the performance issues that can result from a failure to use all three can be truly remarkable.