ERROR 1390 (HY000): Prepared statement contains too many placeholders is raised when a prepared statement contains more than 65,535 ? parameter markers. The error symbol is ER_PS_MANY_PARAM.
Impact
The statement is rejected before execution begins — no rows are inserted, updated, or read. The transaction is not automatically rolled back, but any work in the current batch is lost and must be retried with a corrected statement.
Developers most commonly encounter this during bulk operations. ORMs and query builders that generate a single INSERT statement for a large collection of records multiply the number of parameters quickly: inserting 5,000 rows with 14 columns each produces 70,000 placeholders, exceeding the 65,535 limit. The error surfaces at the point the prepared statement is sent to the server, not during value binding, so the failure can be surprising if the calling code never explicitly counts parameters.
Common Causes
ORM bulk insert with a large batch size. Frameworks like SQLAlchemy, Hibernate, GORM, and ActiveRecord can batch multiple rows into a single
INSERT ... VALUES (?, ?), (?, ?), ...statement. When the batch size is not bounded relative to the number of columns, the placeholder count exceeds 65,535.Manually constructed
IN (?, ?, ...)clauses. Building a query likeWHERE id IN (?)from an application-level list and passing thousands of IDs as individual parameters will breach the limit.Dynamic multi-row
UPDATEorREPLACE INTOgenerators. Code that loops over records and concatenates(?, ?, ?)groups into one statement faces the same arithmetic constraint as bulk inserts.The limit is hard-coded in the protocol. The MySQL client/server protocol stores the parameter count in a two-byte unsigned integer field, so the 65,535 ceiling cannot be raised by configuration — it is a protocol constraint, not a tunable server variable.
Troubleshooting and Resolution Steps
Calculate your actual placeholder count. For a bulk insert, the formula is
rows × columns_per_row. Find the batch size and column count in your ORM or query builder configuration:-- Example: inserting 5,000 rows with 14 columns -- 5,000 × 14 = 70,000 placeholders → exceeds 65,535 -- Safe batch size: floor(65,535 / 14) = 4,681 rows per batchSplit large batches into smaller chunks. The standard fix is to divide the input list into chunks whose placeholder count stays under 65,535 and execute one statement per chunk.
Python / SQLAlchemy example:
CHUNK_SIZE = 4000 # conservative; tune based on column count for i in range(0, len(records), CHUNK_SIZE): session.bulk_insert_mappings(MyModel, records[i:i + CHUNK_SIZE]) session.commit()Raw SQL example (any language):
-- Instead of one INSERT with 70,000 placeholders, -- issue multiple inserts, each within the limit: INSERT INTO orders (user_id, product_id, qty, price, ...) VALUES (?, ?, ?, ?, ...), -- row 1 (?, ?, ?, ?, ...), -- row 2 ... -- up to ~4,500 rows for a 14-column table ;Use a temporary table or
LOAD DATAfor very large datasets. When inserting tens of thousands of rows at once,LOAD DATA INFILEor loading into a staging table via multi-statement (non-prepared) inserts avoids the protocol limit entirely:LOAD DATA LOCAL INFILE '/tmp/orders.csv' INTO TABLE orders FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' (user_id, product_id, qty, price, created_at);Replace large
INlists with a temporary table join. When theINclause is the source of the overflow, load the IDs into a temporary table and join instead:CREATE TEMPORARY TABLE tmp_ids (id INT UNSIGNED NOT NULL PRIMARY KEY); -- Insert IDs in bounded batches (no placeholders issue for small chunks) INSERT INTO tmp_ids (id) VALUES (1001), (1002), ..., (5000); SELECT o.* FROM orders o JOIN tmp_ids t ON o.id = t.id;Check ORM/driver batch settings. Many ORMs expose a
chunk_size,batch_size, orinsert_chunk_sizeoption. Setting it appropriately for your table's column count prevents the error from recurring:# SQLAlchemy: use_insertmanyvalues_page_size (SQLAlchemy 2.x) engine = create_engine(url, insertmanyvalues_page_size=1000)
Additional Information
- The 65,535 placeholder limit is defined by the MySQL binary protocol (
COM_STMT_PREPAREresponse fieldnum_paramsis 2 bytes). It cannot be changed viamy.cnfor any server variable — workarounds must be at the application layer. max_allowed_packetis a separate, unrelated setting that controls the maximum size of a single network packet. Increasing it does not affect the placeholder count limit and will not resolve error 1390.- Error 1390 is distinct from error 1436 (
ER_STMT_HAS_NO_OPEN_CURSOR) and error 1243 (ER_UNKNOWN_STMT_HANDLER), which relate to cursor state rather than parameter counts. - MariaDB enforces the same 65,535 limit and surfaces the same SQLSTATE
HY000with a matching error message. - Some connection poolers (ProxySQL, PlanetScale's driver shim) may translate or rewrite prepared statements; if the error appears unexpectedly in a proxied environment, verify which layer is generating the oversized statement.
Frequently Asked Questions
Why does this only happen in production and not in development? Development datasets are typically small, so batch sizes that stay within the limit during testing overflow it against real data volumes. The number of placeholders scales with the number of rows, not the size of the data — a table with many columns will hit the limit sooner.
Can I increase the 65,535 limit by changing a MySQL server variable?
No. The limit is encoded in the binary protocol and is not configurable. The only solutions are to split operations into smaller batches or use alternatives like LOAD DATA INFILE that bypass the prepared-statement protocol.
Does this affect non-prepared (plain text) statements?
No. Error 1390 is specific to the binary prepared-statement protocol (PREPARE / EXECUTE or driver-level COM_STMT_PREPARE). Plain text queries have no equivalent placeholder limit, though very long query strings may still hit max_allowed_packet.
How do I find the safe batch size for my table?
Divide 65,535 by the number of columns you are inserting: floor(65535 / num_columns). Use a slightly smaller value (e.g., multiply by 0.9) to leave headroom for future schema changes.