PostgreSQL Version | Supported Version |
PostgreSQL 17 | v17.10_r1.19 or later |
PostgreSQL 18 | v18.4_r1.10 or later |
SHOW tencentdb_version;.shared_preload_libraries parameter on the parameter settings page in the console, select pg_savior in its running value, save the changes, and restart the instance.pg_savior.enabled defaults to on), with no additional operations required.CREATE TABLE statement includes PRIMARY KEY or UNIQUE constraints, it is also intercepted by the non-concurrent index creation protection (except for temporary tables). For handling, see FAQ.ANALYZE first to update statistics.user level (they can be set directly in a session via SET) and can also be modified in the console parameter settings.Parameter | Default Value | Description |
pg_savior.enabled | on | Master protection switch. When off, disables all protection. |
pg_savior.bypass | off | Session-level bypass switch. When on, the current session skips all protection. |
pg_savior.max_rows_affected | 0 | Upper limit of estimated affected rows for UPDATE/DELETE. Requests are blocked when the limit is exceeded. 0 means no limit. |
pg_savior.large_table_threshold_rows | 1000000 | Large table threshold (rows). Tables exceeding this row count are protected by DDL protection. |
pg_savior.max_query_cost | 0 | Upper limit of estimated statement execution cost (total_cost). Statements are blocked when the limit is exceeded. 0 means no limit. |
pg_savior.max_result_rows | 0 | Upper limit of returned rows. An error is reported and execution is aborted when the limit is exceeded. 0 means no limit. |
root (a member of tencentdb_superuser). You can directly reproduce the example table created in this section.shared_preload_libraries parameter, select pg_savior in its running value, save the changes, and restart the instance.postgres=> SHOW shared_preload_libraries;shared_preload_libraries--------------------------------------------------------------------------------------------------------pg_textsearch, pg_cron, pg_squeeze, timescaledb, pg_jieba, pg_bigm, tencentdb_pwdcheck, pg_savior(1 row)
pg_savior, the plugin has been loaded. Check the protection parameter status:postgres=> SELECT name, setting, context FROM pg_settings WHERE name LIKE 'pg_savior%' ORDER BY name;name | setting | context--------------------------------------+---------+---------pg_savior.bypass | off | userpg_savior.enabled | on | userpg_savior.large_table_threshold_rows | 1000000 | userpg_savior.max_rows_affected | 0 | user(4 rows)
context is user, all parameters can be adjusted directly in a session via SET without restarting the instance.postgres=> CREATE TABLE orders (id int,customer text,amount numeric);CREATE TABLEpostgres=> INSERT INTO orders VALUES (1, 'alice', 100.00), (2, 'bob', 250.50), (3, 'carol', 78.90);INSERT 0 3postgres=> ANALYZE orders;ANALYZE
postgres=> DELETE FROM orders;ERROR: pg_savior: DELETE without WHERE clause is blockedHINT: Add a WHERE clause, or set pg_savior.bypass = on for this session.postgres=> UPDATE orders SET amount = 0;ERROR: pg_savior: UPDATE without WHERE clause is blockedHINT: Add a WHERE clause, or set pg_savior.bypass = on for this session.
postgres=> WITH moved AS (DELETE FROM orders RETURNING *) SELECT count(*) FROM moved;ERROR: pg_savior: DELETE without WHERE clause is blockedHINT: Add a WHERE clause, or set pg_savior.bypass = on for this session.
postgres=> DELETE FROM orders WHERE id = 0;DELETE 0
pg_savior.max_rows_affected is set, UPDATE/DELETE statements whose optimizer-estimated affected rows exceed this value will be intercepted. In the following example, the limit is set to 2, and the sample table orders contains 3 rows:root=> SET pg_savior.max_rows_affected = 2; DELETE FROM orders WHERE id > 0;SETERROR: pg_savior: DELETE estimated to affect 3 rows, exceeds pg_savior.max_rows_affected (2)HINT: Refine the WHERE clause, raise pg_savior.max_rows_affected, or set pg_savior.bypass = on. Run ANALYZE if the estimate looks wrong.
root=> SET pg_savior.max_rows_affected = 2; UPDATE orders SET amount = 0 WHERE id <= 2;SETUPDATE 2root=> SET pg_savior.max_rows_affected = 0;SET
postgres=> CREATE INDEX idx_orders_customer ON orders (customer);ERROR: pg_savior: CREATE INDEX without CONCURRENTLY is blockedHINT: Use CREATE INDEX CONCURRENTLY (it cannot run in a transaction block), or set pg_savior.bypass = on for this session.
CREATE INDEX CONCURRENTLY to create the index online:postgres=> CREATE INDEX CONCURRENTLY idx_orders_customer ON orders (customer);CREATE INDEX
CREATE INDEX CONCURRENTLY cannot be executed within a transaction block.pg_savior.large_table_threshold_rows rows (default: 1000000) is considered a "large table". Operations such as TRUNCATE, DROP TABLE, and ALTER TABLE that may trigger a full table rewrite will be intercepted. The following example creates a table with more than 1 million rows:postgres=> CREATE TABLE big_orders AS SELECT g AS id, 'customer_' || g AS customer, g::numeric AS amount FROM generate_series(1, 1000100) g;SELECT 1000100postgres=> ANALYZE big_orders;ANALYZE
postgres=> TRUNCATE big_orders;ERROR: pg_savior: TRUNCATE on a large table "big_orders" (1000100 rows) is blockedHINT: Verify the target, raise pg_savior.large_table_threshold_rows, or set pg_savior.bypass = on. Run ANALYZE if the row estimate looks wrong.postgres=> DROP TABLE big_orders;ERROR: pg_savior: DROP TABLE on a large table "big_orders" (1000100 rows) is blockedHINT: Verify the target, raise pg_savior.large_table_threshold_rows, or set pg_savior.bypass = on. Run ANALYZE if the row estimate looks wrong.postgres=> ALTER TABLE big_orders ADD COLUMN memo text DEFAULT 'N';ERROR: pg_savior: ALTER TABLE ADD COLUMN with DEFAULT on a large table (1000100 rows) is blockedHINT: This operation may rewrite the whole table (it is blocked conservatively). Plan a batched migration; raise pg_savior.large_table_threshold_rows; or set pg_savior.bypass = on. Run ANALYZE if the row estimate looks wrong.postgres=> ALTER TABLE big_orders ALTER COLUMN amount TYPE numeric(12,2);ERROR: pg_savior: ALTER TABLE ALTER COLUMN TYPE on a large table (1000100 rows) is blockedHINT: This operation may rewrite the whole table (it is blocked conservatively). Plan a batched migration; raise pg_savior.large_table_threshold_rows; or set pg_savior.bypass = on. Run ANALYZE if the row estimate looks wrong.
ALTER TABLE statements that do not trigger a full table rewrite are not affected:postgres=> ALTER TABLE big_orders ADD COLUMN memo text;ALTER TABLE
postgres=> CREATE TABLE junk (id int);CREATE TABLEpostgres=> DROP TABLE junk;DROP TABLE
ALTER TABLE operations that may be intercepted include ADD COLUMN with DEFAULT and ALTER COLUMN TYPE, both of which may rewrite the entire table.TRUNCATE ... CASCADE are also included in the check.pg_savior.max_query_cost is set, statements whose planner-estimated execution cost (total_cost) exceeds this value will be intercepted before execution. In the following example, the limit is set to 100, and a full table statistics query on the sample table big_orders (with more than 1 million rows) will be intercepted:root=> SET pg_savior.max_query_cost = 100; SELECT count(*) FROM big_orders;SETERROR: pg_savior: statement estimated cost 13569 exceeds pg_savior.max_query_cost (100)HINT: Refine the query, raise pg_savior.max_query_cost, or set pg_savior.bypass = on. Check the estimate with EXPLAIN.
EXPLAIN only generates an execution plan and does not actually execute the statement, so it is not subject to this protection limit. You can use it to view the estimated cost:root=> SET pg_savior.max_query_cost = 100; EXPLAIN SELECT count(*) FROM big_orders;SETQUERY PLAN----------------------------------------------------------------------------------------------Finalize Aggregate (cost=13569.07..13569.08 rows=1 width=8)-> Gather (cost=13568.85..13569.06 rows=2 width=8)Workers Planned: 2-> Partial Aggregate (cost=12568.85..12568.86 rows=1 width=8)-> Parallel Seq Scan on big_orders (cost=0.00..11527.08 rows=416708 width=0)(5 rows)root=> SET pg_savior.max_query_cost = 0;SET
EXPLAIN.EXPLAIN ANALYZE actually executes the statement and is also subject to this protection check.pg_savior.max_result_rows is set, a statement will be terminated with an error if its output row quantity exceeds this value. In the following example, the limit is set to 2, and the sample table orders contains 3 rows:root=> SET pg_savior.max_result_rows = 2; SELECT id FROM orders;SETERROR: pg_savior: statement returned more than 2 rowsHINT: Add a LIMIT, raise pg_savior.max_result_rows, or set pg_savior.bypass = on.
LIMIT is added to the query, it returns normally:root=> SET pg_savior.max_result_rows = 2; SELECT id FROM orders LIMIT 2;SETid----12(2 rows)root=> SET pg_savior.max_result_rows = 0;SET
LIMIT to the query or adjust the upper limit and retry.postgres=> DROP DATABASE postgres;ERROR: pg_savior: DROP DATABASE "postgres" is blockedHINT: Set pg_savior.bypass = on for this session if you really mean it.
DROP DATABASE IF EXISTS on a non-existent database is an idempotent cleanup script practice and is skipped normally:postgres=> DROP DATABASE IF EXISTS savior_demo_never;NOTICE: database "savior_demo_never" does not exist, skippingDROP DATABASE
root=> SET pg_savior.enabled = off; DELETE FROM orders;SETDELETE 3root=> INSERT INTO orders VALUES (1, 'alice', 100.00), (2, 'bob', 250.50), (3, 'carol', 78.90);INSERT 0 3root=> SET pg_savior.enabled = on;SET
root=> SET pg_savior.bypass = on; DELETE FROM orders;SETDELETE 3root=> SET pg_savior.bypass = off;SET
pg_savior.enabled to off in the console parameter settings.bypass and enabled do not affect other sessions.CREATE TABLE with PRIMARY KEY or UNIQUE constraints reports the error "CREATE INDEX without CONCURRENTLY is blocked". How do I handle this?postgres=> CREATE TABLE t3 (id int PRIMARY KEY, v text);ERROR: pg_savior: CREATE INDEX without CONCURRENTLY is blockedHINT: Use CREATE INDEX CONCURRENTLY (it cannot run in a transaction block), or set pg_savior.bypass = on for this session.
postgres=> CREATE TABLE t3 (id int, v text);CREATE TABLEpostgres=> CREATE UNIQUE INDEX CONCURRENTLY t3_id_uidx ON t3(id);CREATE INDEXpostgres=> ALTER TABLE t3 ADD CONSTRAINT t3_pkey PRIMARY KEY USING INDEX t3_id_uidx;NOTICE: ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index "t3_id_uidx" to "t3_pkey"ALTER TABLEpostgres=> DROP TABLE t3;DROP TABLE
ANALYZE first to update statistics, and then retry.pg_savior.large_table_threshold_rows setting in the session or console to confirm whether the threshold has been adjusted to a value less than the number of rows in the table.SET pg_savior.bypass = on; in the current session to bypass the protection. After the operation is complete, change it back to off. The bypass takes effect only for the current session.pg_savior.enabled to off in the session, or change it to off in the console parameter settings. After the change is complete, re-enable the protection.Was this page helpful?
You can also Contact sales or Submit a Ticket for help.
Help us improve! Rate your documentation experience in 5 mins.
Feedback