Skip to content

Commit f8bcc6f

Browse files
Added attribute cursor.prefetchrows to control the number of rows that the
Oracle Client library fetches into internal buffers when a query is executed (#355).
1 parent 0467db9 commit f8bcc6f

File tree

9 files changed

+415
-161
lines changed

9 files changed

+415
-161
lines changed

doc/src/api_manual/connection.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,10 @@ Connection Object
552552
value can make a significant difference in performance (up to 100x) if you
553553
have a small number of statements that you execute repeatedly.
554554

555+
The default value is 20.
556+
557+
See :ref:`Statement Caching <stmtcache>` for more information.
558+
555559
.. note::
556560

557561
This attribute is an extension to the DB API definition.

doc/src/api_manual/cursor.rst

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Cursor Object
3737
instead of the 1 that the DB API recommends. This value means that 100 rows
3838
are fetched by each internal call to the database.
3939

40-
See :ref:`Tuning Fetch Performance <tuningfetch>`.
40+
See :ref:`Tuning Fetch Performance <tuningfetch>` for more information.
4141

4242
.. attribute:: Cursor.bindarraysize
4343

@@ -442,6 +442,21 @@ Cursor Object
442442
immediately and an implied commit takes place.
443443

444444

445+
.. attribute:: Cursor.prefetchrows
446+
447+
This read-write attribute can be used to tune the number of rows that the
448+
Oracle Client library fetches when a query is executed. This value can
449+
reduce the number of round-trips to the database that are required to
450+
fetch rows but at the cost of additional memory. Setting this value to 0
451+
can be useful when the timing of fetches must be explicitly controlled.
452+
453+
See :ref:`Tuning Fetch Performance <tuningfetch>` for more information.
454+
455+
.. note::
456+
457+
The DB API definition does not define this method.
458+
459+
445460
.. method:: Cursor.prepare(statement, [tag])
446461

447462
This can be used before a call to :meth:`~Cursor.execute()` to define the
@@ -451,6 +466,8 @@ Cursor Object
451466
statement will be returned to the statement cache with the given tag. See
452467
the Oracle documentation for more information about the statement cache.
453468

469+
See :ref:`Statement Caching <stmtcache>` for more information.
470+
454471
.. note::
455472

456473
The DB API definition does not define this method.

doc/src/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ User Guide
3535
user_guide/aq.rst
3636
user_guide/cqn.rst
3737
user_guide/txn_management.rst
38+
user_guide/tuning.rst
3839
user_guide/globalization.rst
3940
user_guide/startup.rst
4041
user_guide/ha.rst

doc/src/release_notes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ Version 8.0 (TBD)
5151
available in Oracle Client 20 and higher.
5252
#) Added function :meth:`SodaOperation.fetchArraySize()` available in Oracle
5353
Client 19.5 and higher.
54+
#) Added attribute :attr:`Cursor.prefetchrows` to control the number of rows
55+
that the Oracle Client library fetches into internal buffers when a query
56+
is executed.
5457
#) Internally make use of new mode available in Oracle Client 20 and higher in
5558
order to avoid a round-trip when accessing :attr:`Connection.version` for
5659
the first time.

doc/src/user_guide/sql_execution.rst

Lines changed: 0 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -130,93 +130,6 @@ This code ensures that, once the block is completed, the cursor is closed and
130130
resources have been reclaimed by the database. In addition, any attempt to use
131131
the variable ``cursor`` outside of the block will simply fail.
132132

133-
.. _tuningfetch:
134-
135-
Tuning Fetch Performance
136-
------------------------
137-
138-
For best performance, the cx_Oracle :attr:`Cursor.arraysize` value should be set
139-
before calling :meth:`Cursor.execute()`. The default value is 100. For queries
140-
that return a large number of rows, increasing ``arraysize`` can improve
141-
performance because it reduces the number of :ref:`round-trips <roundtrips>` to
142-
the database. However increasing this value increases the amount of memory
143-
required. The best value for your system depends on factors like your network
144-
speed, the query row size, and available memory. An appropriate value can be
145-
found by experimenting with your application.
146-
147-
Regardless of which fetch method is used to get rows, internally all rows are
148-
fetched in batches corresponding to the value of ``arraysize``. The size does
149-
not affect how, or when, rows are returned to your application (other than being
150-
used as the default size for :meth:`Cursor.fetchmany()`). It does not limit the
151-
minimum or maximum number of rows returned by a query.
152-
153-
Along with tuning ``arraysize``, make sure your `SQL statements are optimal
154-
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=TGSQL>`_ and avoid
155-
selecting columns that are not required by the application. For queries that do
156-
not need to fetch all data, use appropriate ``WHERE`` clauses such as a
157-
:ref:`row limiting clause <rowlimit>` to reduce the number of rows processed by
158-
the database. For small, mostly static, lookup tables enable :ref:`Client Result
159-
Caching <crc>` to avoid round-trips between cx_Oracle and the database. For
160-
queries that return large data or a large number of rows, or when using a slow
161-
network, tune the network `Session Data Unit (SDU) and socket buffer sizes
162-
<https://static.rainfocus.com/oracle/oow19/sess/1553616880266001WLIh/PF/OOW19_Net_CON4641_1569022126580001esUl.pdf>`__.
163-
164-
An example of setting ``arraysize`` is:
165-
166-
.. code-block:: python
167-
168-
cur = connection.cursor()
169-
cur.arraysize = 500
170-
for row in cur.execute("select * from MyTable"):
171-
print(row)
172-
173-
One place where increasing ``arraysize`` is particularly useful is in copying
174-
data from one database to another:
175-
176-
.. code-block:: python
177-
178-
# setup cursors
179-
sourceCursor = sourceConnection.cursor()
180-
sourceCursor.arraysize = 1000
181-
targetCursor = targetConnection.cursor()
182-
targetCursor.arraysize = 1000
183-
184-
# perform fetch and bulk insertion
185-
sourceCursor.execute("select * from MyTable")
186-
while True:
187-
rows = sourceCursor.fetchmany()
188-
if not rows:
189-
break
190-
targetCursor.executemany("insert into MyTable values (:1, :2)", rows)
191-
targetConnection.commit()
192-
193-
If you know that a query returns a small number of rows then you should reduce
194-
the value of ``arraysize``. For example if you are fetching only one row, then
195-
set ``arraysize`` to 1:
196-
197-
.. code-block:: python
198-
199-
cur = connection.cursor()
200-
cur.arraysize = 1
201-
cur.execute("select * from MyTable where id = 1"):
202-
row = cur.fetchone()
203-
print(row)
204-
205-
In cx_Oracle, the ``arraysize`` value is only examined when a statement is
206-
executed the first time. To change the ``arraysize`` for a repeated statement,
207-
create a new cursor:
208-
209-
.. code-block:: python
210-
211-
array_sizes = (10, 100, 1000)
212-
for size in array_sizes:
213-
cursor = connection.cursor()
214-
cursor.arraysize = size
215-
start = time.time()
216-
cursor.execute(sql).fetchall()
217-
elapsed = time.time() - start
218-
print("Time for", size, elapsed, "seconds")
219-
220133
.. _querymetadata:
221134

222135
Query Column Metadata
@@ -808,72 +721,3 @@ SDO_GEOMETRY <spatial>` object:
808721
cur = connection.cursor()
809722
cur.setinputsizes(typeObj)
810723
cur.execute("insert into sometable values (:1)", [None])
811-
812-
.. _roundtrips:
813-
814-
Database Round-trips
815-
====================
816-
817-
A round-trip is defined as the trip from the Oracle Client libraries (:ref:`used by
818-
cx_Oracle <archfig>`) to the database and back. Along with tuning an application's
819-
architecture and tuning its SQL statements, a general performance and
820-
scalability goal is to minimize `round-trips
821-
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-9B2F05F9-D841-4493-A42D-A7D89694A2D1>`__.
822-
823-
Some general tips for reducing round-trips are:
824-
825-
- Tune :attr:`Cursor.arraysize`, see :ref:`Tuning Fetch Performance <tuningfetch>`.
826-
- Use :meth:`Cursor.executemany()` for optimal DML execution, see :ref:`Batch Statement Execution and Bulk Loading <batchstmnt>`.
827-
- Only commit when necessary. Use :attr:`Connection.autocommit` on the last statement of a transaction.
828-
- For connection pools, use a callback to set connection state, see :ref:`Session CallBacks for Setting Pooled Connection State <sessioncallback>`.
829-
- Make use of PL/SQL procedures which execute multiple SQL statements instead of executing them individually from cx_Oracle.
830-
- Use scalar types instead of :ref:`Oracle named object types <fetchobjects>`
831-
- Avoid overuse of :meth:`Connection.ping()`.
832-
833-
Oracle's `Automatic Workload Repository (AWR)
834-
<https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-56AEF38E-9400-427B-A818-EDEC145F7ACD>`__
835-
reports show 'SQL*Net roundtrips to/from client' and are useful for finding the
836-
overall behavior of a system.
837-
838-
Sometimes you may wish to find the number of round-trips used for a
839-
specific application. Snapshots of the ``V$SESSTAT`` view taken before
840-
and after doing some work can be used for this.
841-
842-
First, find the session id of the current connection:
843-
844-
.. code-block:: python
845-
846-
cursor.execute("select sys_context('userenv','sid') from dual")
847-
sid, = cursor.fetchone();
848-
849-
This can be used with ``V$SESSTAT`` to find the current number of round-trips.
850-
A second connection should be used to avoid affecting the count. If your user
851-
does not have access to the V$ views, then use a SYSTEM connection:
852-
853-
.. code-block:: python
854-
855-
def getRT(conn, sid):
856-
cursor = conn.cursor()
857-
cursor.execute(
858-
"""SELECT ss.value
859-
FROM v$sesstat ss, v$statname sn
860-
WHERE ss.sid = :sid
861-
AND ss.statistic# = sn.statistic#
862-
AND sn.name LIKE '%roundtrip%client%'""", sid = sid)
863-
rt, = cursor.fetchone();
864-
return rt
865-
866-
The main part of a benchmark application can perform "work" and use ``getRT()``
867-
to calculate the number of round-trips the work required:
868-
869-
.. code-block:: python
870-
871-
rt = getRT(systemconn, sid)
872-
873-
cursor = conn.cursor()
874-
cursor.execute("select * from dual")
875-
row = cursor.fetchone()
876-
877-
rt = getRT(systemconn, sid) - rt
878-
879-
print("Round-trips", rt)

0 commit comments

Comments
 (0)