Sửa lỗi PostgreSQL 'ERROR: function does not exist' Do search_path Sai

intermediate🐘 PostgreSQL2026-04-07| PostgreSQL 12–16, Linux/macOS/Windows, mọi client (psql, pgAdmin, ứng dụng)

Error Message

ERROR: function function_name(unknown) does not exist
#postgresql#function#schema#search-path

Lỗi Gặp Phải

Bạn gọi một stored function và PostgreSQL báo lỗi:

ERROR:  function send_welcome_email(unknown) does not exist
LINE 1: SELECT send_welcome_email('user@example.com');
               ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

Thật bực bội, vì bạn biết chắc function đó tồn tại. Chính bạn đã viết nó. Bạn còn nhìn thấy nó trong pgAdmin ngay lúc này. Vậy mà khi gọi từ ứng dụng, từ một role khác, hoặc từ một phiên psql mới — PostgreSQL cứ nhìn bạn trống không.

Nguyên Nhân

Khi bạn gọi một function, PostgreSQL không tìm kiếm toàn bộ database. Nó kiểm tra các schema theo thứ tự xác định bởi search_path. Mặc định thứ tự này là "$user", public — nghĩa là nó tìm trong schema trùng tên với user hiện tại, sau đó là public, và không tìm ở đâu khác nữa.

Nếu bạn đặt function trong một schema tùy chỉnh như app hay myschema mà quên thêm schema đó vào search_path, PostgreSQL thực sự không tìm thấy nó. Thông báo lỗi hoàn toàn chính xác, dù cảm giác có vẻ sai.

Vậy tại sao lại dễ mắc phải? Một vài tình huống thường gặp:

  • Function được tạo trong schema không phải public (thường xảy ra với ORM và migration tool)
  • Flyway, Liquibase, hoặc Alembic có chạy SET search_path trong lúc migration nhưng ứng dụng lại kết nối mà không có lệnh đó
  • Role của ứng dụng có search_path mặc định khác với tài khoản admin bạn dùng để tạo function
  • Nâng cấp phiên bản lớn PostgreSQL đã âm thầm reset cài đặt search path ở cấp role

Bước 1 — Xác Định Function Đang Nằm Ở Đâu

Trước khi sửa bất cứ điều gì, hãy xác nhận xem function đang ở đâu. Mở psql và chạy:

SELECT n.nspname AS schema, p.proname AS function_name
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE p.proname = 'send_welcome_email';

Kết quả ví dụ:

 schema |      function_name
--------+--------------------
 app    | send_welcome_email
(1 row)

Đây rồi — schema app, không phải public. Chỉ một thông tin này thôi là đủ giải thích tất cả.

Bước 2 — Kiểm Tra search_path Hiện Tại

SHOW search_path;
   search_path
-----------------
 "$user", public
(1 row)

app không có trong danh sách. Mọi lời gọi không kèm schema tới send_welcome_email() đều sẽ thất bại cho đến khi điều đó thay đổi.

Bước 3 — Chọn Cách Sửa

Phương án A: Gọi function kèm tên schema (nhanh nhất, không cần cấu hình)

Thêm tiền tố tên schema vào mỗi lần gọi:

SELECT app.send_welcome_email('user@example.com');

Không cần thay đổi database. Phù hợp cho script chạy một lần hoặc khi function chỉ được gọi ở hai ba chỗ. Sẽ rất mệt nếu bạn gọi nó ở khắp nơi.

Phương án B: Đặt search_path cho phiên hiện tại

Thêm schema chỉ cho kết nối hiện tại:

SET search_path TO app, public;
SELECT send_welcome_email('user@example.com');

Sẽ reset khi phiên kết thúc. Các kết nối khác không bị ảnh hưởng — an toàn để kiểm tra và debug mà không có tác dụng phụ.

Phương án C: Đặt search_path cố định cho một role (khuyến nghị cho ứng dụng)

Khi ứng dụng luôn kết nối bằng cùng một role, đây là giải pháp gọn gàng nhất:

ALTER ROLE app_user SET search_path TO app, public;

Mọi kết nối mới của app_user sẽ tự động có schema app. Các phiên đang chạy không bị ảnh hưởng — hãy khởi động lại ứng dụng hoặc kết nối lại để thấy hiệu lực.

Phương án D: Đặt search_path ở cấp database

Tất cả các role trên database này cần cùng thứ tự schema? Đặt một lần ở cấp database:

ALTER DATABASE mydb SET search_path TO app, public;

Cài đặt ở cấp role sẽ được ưu tiên hơn, nên phương án này không ghi đè Phương án C. Đây là đòn bẩy rộng nhất có thể dùng — hãy thận trọng trên các database dùng chung.

Phương án E: Nhúng search_path trực tiếp vào function (linh hoạt nhất)

PostgreSQL cho phép bạn gắn search_path trực tiếp vào định nghĩa function. Function khi đó sẽ phân giải tên theo schema của chính nó, bất kể ai gọi hay phiên làm việc của họ trông như thế nào:

ALTER FUNCTION app.send_welcome_email(text)
  SET search_path = app, public;

Hoặc đặt ngay khi tạo function:

CREATE OR REPLACE FUNCTION app.send_welcome_email(email text)
RETURNS void
LANGUAGE plpgsql
SET search_path = app, public
AS $$
BEGIN
  -- thân hàm
END;
$$;

Đây là cách khó quên nhất và dễ lý giải nhất. Nó còn ngăn chặn tấn công schema-injection khi một schema độc hại được chèn vào đầu search path của superuser. Xứng đáng thêm một dòng code.

Bước 4 — Xác Minh Kết Quả

Sau khi áp dụng phương án đã chọn, hãy chạy kiểm tra nhanh:

SELECT send_welcome_email('test@example.com');

Dùng Phương án C hoặc D? Hãy mở một kết nối hoàn toàn mới trước — những thay đổi đó chỉ áp dụng cho các phiên bắt đầu sau lệnh ALTER.

Để xác nhận search path hiệu lực của role sau lệnh ALTER ROLE:

-- Kết nối lại với tư cách app_user, rồi chạy:
SHOW search_path;
  search_path
--------------
 app, public
(1 row)

Kết quả này có nghĩa role đã được cấu hình đúng và các kết nối mới sẽ tìm thấy function mà không cần thêm bất kỳ thiết lập nào.

Xử Lý Lỗi Trong Ứng Dụng

Khi lỗi phát sinh từ code ứng dụng — Node.js, Python, Java — cách gọn nhất là dùng connection hook để chạy SET search_path ngay sau khi mỗi kết nối mới được thiết lập.

Node.js (node-postgres):

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

pool.on('connect', (client) => {
  client.query("SET search_path TO app, public");
});

Python (psycopg2):

import psycopg2

conn = psycopg2.connect(dsn)
conn.cursor().execute("SET search_path TO app, public")
conn.commit()

SQLAlchemy (Python):

from sqlalchemy import event, text

@event.listens_for(engine, 'connect')
def set_search_path(dbapi_connection, connection_record):
    cursor = dbapi_connection.cursor()
    cursor.execute("SET search_path TO app, public")
    cursor.close()

Thêm: Kiểm Tra Tất Cả Function và Schema Của Chúng

Không chắc có bao nhiêu function đang ẩn trong các schema không phải public? Query sau liệt kê tất cả function do người dùng định nghĩa cùng với schema và chữ ký của chúng:

SELECT n.nspname AS schema,
       p.proname AS function,
       pg_get_function_identity_arguments(p.oid) AS arguments
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY n.nspname, p.proname;

Bất kỳ function nào nằm ngoài public đều là ứng viên gây ra lỗi này nếu người gọi không thêm schema đó vào search_path. Hãy chạy query này sau khi migration hoặc nâng cấp phiên bản lớn — chỉ mất hai giây nhưng đã phát hiện ra nhiều điều bất ngờ.

Tóm Tắt Nhanh

  • Sửa nhanh một lần: schema.function_name() — không cần cấu hình
  • Một phiên duy nhất: SET search_path TO schema, public
  • Theo role (tốt nhất cho ứng dụng): ALTER ROLE role SET search_path TO schema, public
  • Theo database: ALTER DATABASE db SET search_path TO schema, public
  • Theo function (linh hoạt nhất): ALTER FUNCTION ... SET search_path = schema, public

Related Error Notes