Skip to content

Parameterised queries run much more slowly than un-parameterised queries, even when there are no parameters #238

@inglesp

Description

@inglesp

What happens?

Some parameterised queries run much slower than the equivalent non-parameterised queries, even when parameterised with an empty list.

For the examples in the repro script below, the parameterised queries take 4.6s to run, and the un-parameterised query takes 1.2s.

python version: 3.14.0 (main, Oct 28 2025, 12:13:17) [Clang 20.1.4 ]
duckdb version: v1.4.3
--------------------------------------------------------------------------------
sql: SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE ?
params: ['03%']
elapsed:  4.578065395355225
--------------------------------------------------------------------------------
sql: SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE '03%'
params: []
elapsed:  4.612151384353638
--------------------------------------------------------------------------------
sql: SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE '03%'
params: None
elapsed:  1.2341203689575195

(With production data and queries, we see parameterised queries take ~40x longer than the equivalent non-parameterised ones.)

EXPLAIN ANALYZE shows the same query plan for each query:

`EXPLAIN ANALYZE`
python version: 3.14.0 (main, Oct 28 2025, 12:13:17) [Clang 20.1.4 ]
duckdb version: v1.4.3
--------------------------------------------------------------------------------
sql: SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE ?
params: ['03%']
┌─────────────────────────────────────┐
│┌───────────────────────────────────┐│
││    Query Profiling Information    ││
│└───────────────────────────────────┘│
└─────────────────────────────────────┘
EXPLAIN ANALYSE SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE ?
┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││              Total Time: 0.0632s             ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌───────────────────────────┐
│           QUERY           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│      EXPLAIN_ANALYZE      │
│    ────────────────────   │
│           0 rows          │
│          (0.00s)          │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         PROJECTION        │
│    ────────────────────   │
│           value           │
│                           │
│      10,000,000 rows      │
│          (0.00s)          │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         HASH_JOIN         │
│    ────────────────────   │
│      Join Type: INNER     │
│                           │
│        Conditions:        ├──────────────┐
│         t2_id = id        │              │
│                           │              │
│      10,000,000 rows      │              │
│          (0.03s)          │              │
└─────────────┬─────────────┘              │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         TABLE_SCAN        ││         TABLE_SCAN        │
│    ────────────────────   ││    ────────────────────   │
│         Table: t1         ││         Table: t2         │
│   Type: Sequential Scan   ││   Type: Sequential Scan   │
│                           ││      Projections: id      │
│        Projections:       ││                           │
│           t2_id           ││          Filters:         │
│           value           ││  code>='03' AND code<'04' │
│                           ││                           │
│      10,000,000 rows      ││        10,000 rows        │
│          (0.67s)          ││          (0.00s)          │
└───────────────────────────┘└───────────────────────────┘

--------------------------------------------------------------------------------
sql: SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE '03%'
params: []
┌─────────────────────────────────────┐
│┌───────────────────────────────────┐│
││    Query Profiling Information    ││
│└───────────────────────────────────┘│
└─────────────────────────────────────┘
EXPLAIN ANALYSE SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE '03%'
┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││              Total Time: 0.0624s             ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌───────────────────────────┐
│           QUERY           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│      EXPLAIN_ANALYZE      │
│    ────────────────────   │
│           0 rows          │
│          (0.00s)          │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         PROJECTION        │
│    ────────────────────   │
│           value           │
│                           │
│      10,000,000 rows      │
│          (0.00s)          │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         HASH_JOIN         │
│    ────────────────────   │
│      Join Type: INNER     │
│                           │
│        Conditions:        ├──────────────┐
│         t2_id = id        │              │
│                           │              │
│      10,000,000 rows      │              │
│          (0.03s)          │              │
└─────────────┬─────────────┘              │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         TABLE_SCAN        ││         TABLE_SCAN        │
│    ────────────────────   ││    ────────────────────   │
│         Table: t1         ││         Table: t2         │
│   Type: Sequential Scan   ││   Type: Sequential Scan   │
│                           ││      Projections: id      │
│        Projections:       ││                           │
│           t2_id           ││          Filters:         │
│           value           ││  code>='03' AND code<'04' │
│                           ││                           │
│      10,000,000 rows      ││        10,000 rows        │
│          (0.65s)          ││          (0.00s)          │
└───────────────────────────┘└───────────────────────────┘

--------------------------------------------------------------------------------
sql: SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE '03%'
params: None
┌─────────────────────────────────────┐
│┌───────────────────────────────────┐│
││    Query Profiling Information    ││
│└───────────────────────────────────┘│
└─────────────────────────────────────┘
EXPLAIN ANALYSE SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE '03%'
┌────────────────────────────────────────────────┐
│┌──────────────────────────────────────────────┐│
││              Total Time: 0.108s              ││
│└──────────────────────────────────────────────┘│
└────────────────────────────────────────────────┘
┌───────────────────────────┐
│           QUERY           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│      EXPLAIN_ANALYZE      │
│    ────────────────────   │
│           0 rows          │
│          (0.00s)          │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         PROJECTION        │
│    ────────────────────   │
│           value           │
│                           │
│      10,000,000 rows      │
│          (0.00s)          │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         HASH_JOIN         │
│    ────────────────────   │
│      Join Type: INNER     │
│                           │
│        Conditions:        ├──────────────┐
│         t2_id = id        │              │
│                           │              │
│      10,000,000 rows      │              │
│          (0.05s)          │              │
└─────────────┬─────────────┘              │
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│         TABLE_SCAN        ││         TABLE_SCAN        │
│    ────────────────────   ││    ────────────────────   │
│         Table: t1         ││         Table: t2         │
│   Type: Sequential Scan   ││   Type: Sequential Scan   │
│                           ││      Projections: id      │
│        Projections:       ││                           │
│           t2_id           ││          Filters:         │
│           value           ││  code>='03' AND code<'04' │
│                           ││                           │
│      10,000,000 rows      ││        10,000 rows        │
│          (1.17s)          ││          (0.00s)          │
└───────────────────────────┘└───────────────────────────┘

To Reproduce

Here's a script that demonstrates the problem. Note that EXPLAIN ANALYSE shows the same plan for each query.

Run with uv run demo.py demo.db.

# /// script
# dependencies = [
#   "duckdb",
# ]
# ///

import sys
import time

import duckdb


T1_ROWS = 1_000_000_000
T2_ROWS = 1_000_000


def print_versions():
    print(f"python version: {sys.version}")
    conn = duckdb.connect(sys.argv[1])
    print("duckdb version:", conn.execute("SELECT version()").fetchall()[0][0])


def setup():
    conn = duckdb.connect(sys.argv[1])
    conn.execute(f"""
        CREATE TABLE IF NOT EXISTS t1 AS
        SELECT i AS value, i % {T2_ROWS} AS t2_id
        FROM range(0, {T1_ROWS}) AS r(i)
    """)
    conn.execute(f"""
        CREATE TABLE IF NOT EXISTS t2 AS
        SELECT i AS id, lpad(CAST(i AS VARCHAR), 6, '0') AS code
        FROM range(0, {T2_ROWS}) AS r(i)
    """)
    conn.execute("CREATE INDEX IF NOT EXISTS idx_t2_code ON t2(code)")


def time_query(sql, params):
    conn = duckdb.connect(sys.argv[1])
    print("-" * 80)
    print("sql:", sql)
    print("params:", params)
    t0 = time.time()
    conn.sql(sql, params=params).fetchall()
    print("elapsed: ", time.time() - t0)

    results = conn.sql(f"EXPLAIN ANALYSE {sql}", params=params).fetchall()
    print(results[0][1])


print_versions()

setup()

time_query(
    "SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE ?",
    params=["03%"],
)
time_query(
    "SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE '03%'",
    params=None,
)
time_query(
    "SELECT t1.value FROM t1 JOIN t2 ON t1.t2_id = t2.id WHERE t2.code LIKE '03%'",
    params=[],
)

OS:

Ubuntu

DuckDB Package Version:

1.4.3

Python Version:

3.14

Full Name:

Peter Inglesby

Affiliation:

Bennett Institute of Applied Data Science, Oxford University

What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a stable release

Did you include all relevant data sets for reproducing the issue?

Yes

Did you include all code required to reproduce the issue?

  • Yes, I have

Did you include all relevant configuration to reproduce the issue?

  • Yes, I have

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions