python-oracledb is Oracleโs official open-source driver for Python, enabling Python applications to connect seamlessly to Oracle Database for SQL, PL/SQL, and advanced data operations.
- โ
Successor to
cx_Oracle(fully compatible) - โ Supports both Thin (no client needed) and Thick (client library) modes
- โ Implements the Python DB API 2.0 standard
- โ Built for performance, scalability, and modern async use
python-oracledb operates in two modes โ each suited for different environments.
- Pure Python (no Oracle Client installation)
- Simple deployment and lightweight
- Ideal for containers, cloud apps, and quick scripts
- Works with Oracle DB 12.1 and later
- Limited access to advanced Oracle features
- Uses Oracle Client libraries (like Instant Client)
- Enables full Oracle Database feature set
- Supports older Oracle versions (as far back as 9.2)
- Reads Oracle network files (
tnsnames.ora,sqlnet.ora,oraaccess.xml)
import oracledb
oracledb.init_oracle_client(lib_dir="/opt/oracle/instantclient_21_12")pip install oracledb- Works on Windows, macOS, and Linux
- Python 3.7+ required
- For Thick mode: install Oracle Instant Client separately
| Component | Supported Versions |
|---|---|
| Python | 3.7 โ 3.14 |
| Oracle DB | 11.2 โ 23c |
| Modes | Thin / Thick |
| OS | Windows, Linux, macOS |
import oracledb
conn = oracledb.connect(
user="hr",
password="hr",
dsn="localhost/orclpdb1"
)with oracledb.connect(user="hr", password="hr", dsn="localhost/orclpdb1") as conn:
with conn.cursor() as cur:
cur.execute("SELECT first_name, last_name FROM employees")
for row in cur:
print(row)๐ช Context managers automatically clean up connections and cursors.
Connection pools boost performance by reusing connections instead of recreating them.
pool = oracledb.create_pool(
user="hr",
password="hr",
dsn="localhost/orclpdb1",
min=2,
max=10,
increment=1
)
conn = pool.acquire()
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM employees")
print(cur.fetchone())
pool.release(conn)๐ก Tip: Always reuse connections in web or multi-threaded apps.
cur.execute(
"SELECT employee_id, first_name FROM employees WHERE department_id = :dept",
dept=50
)
for emp_id, fname in cur:
print(emp_id, fname)cur.execute(
"INSERT INTO employees (employee_id, first_name, last_name) VALUES (:1, :2, :3)",
(300, "John", "Doe")
)
conn.commit()rows = [
(301, "Alice", "Brown"),
(302, "Bob", "Green"),
]
cur.executemany(
"INSERT INTO employees (employee_id, first_name, last_name) VALUES (:1, :2, :3)",
rows
)
conn.commit()cur.callproc("update_salary", [101, 5000])Oracleโs Simple Oracle Document Access (SODA) API enables document-style access to JSON data.
cur.execute("SELECT data FROM json_table WHERE data.name = :name", name="Alice")
for row in cur:
print(row[0]) # Returns a Python dictdb = conn.db
collection = db.create_collection("mydocs")
collection.insert_one({"id": "1", "name": "test", "value": 100})
result = collection.find().filter({"value": {"$gt": 50}}).get_one()
print(result)| Feature | Description |
|---|---|
| Array Binding | Insert/update multiple rows efficiently |
| LOB Support | Handle CLOB, BLOB, and NCLOB data |
| Scrollable Cursors | Move backward and forward through results |
| Implicit Results | Retrieve multiple result sets from PL/SQL |
| Continuous Query Notification (CQN) | Receive notifications when data changes |
| Advanced Queuing (AQ) | Use Oracle message queues |
| Session Tagging | Manage pooled session state |
| Client Result Caching | Cache query results on client side |
| SODA | Work with JSON documents directly |
| High Availability | Support for Application Continuity and FAN |
| Async I/O | Run async queries for high concurrency |
import asyncio
import oracledb
async def main():
async with await oracledb.connect_async(
user="hr", password="hr", dsn="localhost/orclpdb1"
) as conn:
async with conn.cursor() as cur:
await cur.execute("SELECT first_name FROM employees")
async for row in cur:
print(row)
asyncio.run(main())| Change | Description |
|---|---|
| Keyword-only parameters | connect() and create_pool() no longer take positional args |
| Removed parameters | encoding, nencoding, threaded removed |
| Default pool behavior | Now waits for free connections instead of raising errors |
| JSON columns | Fetched as native Python dicts/lists |
| Error codes | New standardized DPY- prefix |
| Renamed classes | SessionPool โ ConnectionPool |
๐ง Migration is straightforward โ most cx_Oracle scripts need only small adjustments.
try:
cur.execute("SELECT * FROM invalid_table")
except oracledb.DatabaseError as e:
print("Database error:", e)DatabaseErrorInterfaceErrorOperationalErrorProgrammingErrorIntegrityError
- โ Use connection pooling
- โ Batch inserts with executemany()
- โ Commit in controlled batches
- โ Use fetchmany() for large datasets
- โ Tune statement caching and fetch size
- โ Use context managers for auto-cleanup
- โ Handle timeouts and dead connections gracefully
- โ Use Thin mode unless advanced features are required
- โ Avoid unnecessary conversions
- โ Keep Instant Client up to date if using Thick mode
- Thin mode does not support every Oracle feature (e.g., DRCP, advanced encryption)
- Must call
init_oracle_client()before any connection in Thick mode - Some features (AQ, SODA) require specific database versions
- Large LOB operations may require streaming techniques
with oracledb.connect(user="hr", password="hr", dsn="localhost/orclpdb1") as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM departments")
for row in cur:
print(row)pool = oracledb.create_pool(user="hr", password="hr", dsn="localhost/orclpdb1")
conn = pool.acquire()
with conn.cursor() as cur:
cur.execute("SELECT COUNT(*) FROM employees")
print(cur.fetchone())
pool.release(conn)async with await oracledb.connect_async(user="hr", password="hr", dsn="localhost/orclpdb1") as conn:
async with conn.cursor() as cur:
await cur.execute("SELECT first_name FROM employees")
async for row in cur:
print(row)- ๐ Use Thin mode for simplicity, Thick mode for advanced use
- ๐งฉ Always initialize the Oracle client early in Thick mode
- ๐ Use parameterized queries to prevent SQL injection
- ๐พ Manage transactions explicitly โ avoid autocommit
- ๐ฆ Reuse connections (especially in APIs and web apps)
- ๐ฆ Implement retry logic for transient network/database errors
- ๐งญ Use SODA and JSON features for flexible data storage
- ๐งฐ Tune and test your connection pool under real workloads
