The SQL Hints mechanism allows users to influence SQL execution by adding hints within comments, enabling custom execution plans. Tacnode supports this capability via the pg_hint_plan extension. This lets developers guide the optimizer to generate specific plans in certain scenarios, improving performance. Especially useful when the default optimizer cannot make optimal decisions, such as with insufficient statistics or complex queries.
pg_hint_plan injects hints via SQL comments. If table aliases are used, parameters must match the alias. Syntax:
/*+ hint_name([t_alias] options) */SELECT * FROM table_name t_alias WHERE ...
Scan Method Control
Specify scan method for queries. Supported: SeqScan, IndexScan.
/*+ SeqScan(t_alias) */SELECT * FROM table_name t_alias WHERE col = 100;/*+ IndexScan(t_alias index_name) */SELECT * FROM table_name t_alias WHERE col = 100;
Join Method Control
Specify join order and algorithm. Supported: NestLoop, HashJoin, MergeJoin.
/*+ HashJoin(table1 table2) */SELECT * FROM table1 JOIN table2 USING (id);/*+ NestLoop(table1 table2) */SELECT * FROM table1 JOIN table2 USING (id);
Force Join Order
Override join order. Use parentheses to group join sides; for multiple tables, nest parentheses to form a tree structure.
/*+ Leading(((table1 table2) table3)) */SELECT * FROM table1JOIN table2 ON table1.id = table2.idJOIN table3 ON table2.id = table3.id;
Create sample tables: users (user info), orders (order info), and products (product info), with necessary indexes.
-- Create users tableCREATE TABLE users ( user_id SERIAL PRIMARY KEY, name TEXT NOT NULL, age INT NOT NULL);-- Create orders tableCREATE TABLE orders ( order_id SERIAL PRIMARY KEY, user_id INT NOT NULL, product_id INT NOT NULL, order_date DATE NOT NULL, amount NUMERIC(10, 2) NOT NULL, FOREIGN KEY (user_id) REFERENCES users(user_id));-- Create products tableCREATE TABLE products ( product_id SERIAL PRIMARY KEY, product_name TEXT NOT NULL, price NUMERIC(10, 2) NOT NULL);-- Create indexes on orders.user_id and orders.product_idCREATE INDEX idx_orders_user_id ON orders(user_id);CREATE INDEX idx_orders_product_id ON orders(product_id);
Insert test data for query validation.
-- Insert data into usersINSERT INTO users (name, age)SELECT 'User_' || i, 20 + (i % 30)FROM generate_series(1, 1000) AS i;-- Insert data into productsINSERT INTO products (product_name, price)SELECT 'Product_' || i, (random() * 100)::NUMERIC(10, 2)FROM generate_series(1, 100) AS i;-- Insert data into ordersINSERT INTO orders (user_id, product_id, order_date, amount)SELECT (i % 1000) + 1, (i % 100) + 1, CURRENT_DATE - (i % 365), (random() * 1000)::NUMERIC(10, 2)FROM generate_series(1, 10000) AS i;
To query users and their orders, the optimizer may select a suboptimal join order. Use the Leading hint to specify join order.
-- Query users older than 30, orders with amount > 500, and related product info.-- Force join order/*+ Leading(((u o) p)) */SELECT u.user_id, u.name, o.order_id, o.amount, p.product_name, p.priceFROM users uJOIN orders o ON u.user_id = o.user_idJOIN products p ON o.product_id = p.product_idWHERE u.age > 30 AND o.amount > 500 AND p.price > 50;
Verify execution plan:
/*+ Leading(((u o) p)) */EXPLAINSELECT u.user_id, u.name, o.order_id, o.amount, p.product_name, p.priceFROM users uJOIN orders o ON u.user_id = o.user_idJOIN products p ON o.product_id = p.product_idWHERE u.age > 30 AND o.amount > 500 AND p.price > 50;
To optimize further, place products first in the join order.
-- Adjust join order/*+ Leading((u (p o))) */SELECT u.user_id, u.name, o.order_id, o.amount, p.product_name, p.priceFROM users uJOIN orders o ON u.user_id = o.user_idJOIN products p ON o.product_id = p.product_idWHERE u.age > 30 AND o.amount > 500 AND p.price > 50;
Verify execution plan:
/*+ Leading((u (p o))) */EXPLAINSELECT u.user_id, u.name, o.order_id, o.amount, p.product_name, p.priceFROM users uJOIN orders o ON u.user_id = o.user_idJOIN products p ON o.product_id = p.product_idWHERE u.age > 30 AND o.amount > 500 AND p.price > 50;
In some cases, force the use of nested loop join (Nested Loop):
-- Force nested loop join/*+ NestLoop(u o p) */SELECT u.user_id, u.name, o.order_id, o.amount, p.product_name, p.priceFROM users uJOIN orders o ON u.user_id = o.user_idJOIN products p ON o.product_id = p.product_idWHERE u.age > 30 AND o.amount > 500 AND p.price > 50;
Verify execution plan:
/*+ NestLoop(u o p) */EXPLAINSELECT u.user_id, u.name, o.order_id, o.amount, p.product_name, p.priceFROM users uJOIN orders o ON u.user_id = o.user_idJOIN products p ON o.product_id = p.product_idWHERE u.age > 30 AND o.amount > 500 AND p.price > 50;
To force a hash join when the optimizer chooses nested loop:
-- Force hash join/*+ HashJoin(u o p) */SELECT u.user_id, u.name, o.order_id, o.amount, p.product_name, p.priceFROM users uJOIN orders o ON u.user_id = o.user_idJOIN products p ON o.product_id = p.product_idWHERE u.age > 30 AND o.amount > 500 AND p.price > 50;
Verify execution plan:
/*+ HashJoin(u o p) */EXPLAINSELECT u.user_id, u.name, o.order_id, o.amount, p.product_name, p.priceFROM users uJOIN orders o ON u.user_id = o.user_idJOIN products p ON o.product_id = p.product_idWHERE u.age > 30 AND o.amount > 500 AND p.price > 50;
The structure of the hint_table is as follows. By inserting rules into this table, you can dynamically adjust the behavior of pg_hint:
CREATE TABLE hint_plan.hints ( id serial PRIMARY KEY, query_id text NOT NULL, -- Unique hash identifier for the query application_name text NOT NULL, -- Application name filter; set to an empty string if not used hints text NOT NULL -- Hint content UNIQUE (query_id, application_name));
Feature Support Limitations: For complex queries, hints may be ignored by the optimizer. Always verify the actual execution plan repeatedly via EXPLAIN.
Hint Syntax Requirements: Hints must strictly follow the extension’s syntax; otherwise, they are ignored.
Statistics Accuracy: Make sure the table's statistics are up-to-date to avoid generating suboptimal plans.
Avoid Over-optimization: Use hints only when the optimizer cannot deliver an ideal plan; prioritize relying on the native Tacnode statistical model.