CTE를 사용한 SQL 함수의 PostgreSQL 9.6 열 삭제 및 부작용


15

A, B 및 D와 같은 3 개의 열이있는 테이블이 있고 D를 현재 위치로 대체하기 위해 C를 말해야하는 경우 다음 방법을 사용합니다.

  1. C와 D2로 2 개의 새로운 컬럼을 소개합니다.
  2. D의 내용을 D2로 복사하십시오.
  3. D를 삭제하십시오.
  4. D2를 D로 이름을 바꿉니다.

새로운 주문은 A, B, C 및 D입니다.

나는 이것이 지금까지 아무런 문제가 없었기 때문에 이것이 합법적 인 관행이라고 생각했다.

그러나 오늘 같은 테이블에서 명령문을 수행하는 함수가 다음 오류를 반환했을 때 문제가 발생했습니다.

table row type and query-specified row type do not match

그리고 다음 세부 사항 :

Query provides a value for a dropped column at ordinal position 13

PostgreSQL을 다시 시작하고 여기여기에VACUUM FULL 제안 된대로 함수를 삭제하고 다시 작성 하려고 시도했지만 이러한 솔루션은 작동하지 않았습니다 (시스템 테이블이 변경된 상황을 해결하려고한다는 사실은 제외).

아주 작은 데이터베이스로 작업하는 것이 고급스러워서 데이터베이스를 내보내고 삭제 한 다음 다시 가져 와서 내 기능 관련 문제해결 했습니다.


나는 다음과 같이 시스템 테이블을 수정하여 손을 더럽히는 pg_attribute등 의 자연스러운 순서로 엉망이되어서는 안된다는 사실을 알고 있었다 .

Postgres에서 열의 자연 순서를 변경할 수 있습니까?

내 함수에 의해 발생하는 오류로 판단하면 이제 내 방법으로 열 순서를 변경하는 것도 무의미하다는 것을 알았습니다. 내가하고있는 일이 왜 잘못되었는지에 대해 누군가가 빛을 발할 수 있습니까?


Postgres 버전은 9.6.0입니다.

기능은 다음과 같습니다.

CREATE OR REPLACE FUNCTION "public"."__post_users" ("facebookid" text, "useremail" text, "username" text) RETURNS TABLE (authentication_code text, id integer, key text, stripe_id text) AS '

-- First, select the user:
WITH select_user AS
(SELECT
users.id
FROM
users
WHERE
useremail = users.email),

-- Second, update the user (if user exists):
update_user AS
(UPDATE
users
SET
authentication_code = GEN_RANDOM_UUID(),
authentication_date = current_timestamp,
facebook_id = facebookid
WHERE EXISTS (SELECT * FROM select_user)
AND
useremail = users.email
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id),

-- Third, insert the user (if user does not exist):
insert_user AS
(INSERT INTO
users (authentication_code, authentication_date, email, key, name, facebook_id)
SELECT
GEN_RANDOM_UUID(),
current_timestamp,
useremail,
GEN_RANDOM_UUID(),
COALESCE(username, SUBSTRING(useremail FROM ''([^@]+)'')),
facebookid
WHERE NOT EXISTS (SELECT * FROM select_user)
RETURNING
users.authentication_code,
users.id,
users.key,
users.stripe_id)

-- Finally, select the authentication code, ID, key and Stripe ID:
SELECT
*
FROM
update_user
UNION ALL
SELECT
*
FROM
insert_user' LANGUAGE "sql" COST 100 ROWS 1
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER

나는 모두 열 재정렬 / 이름 변경을 수행 facebook_id하고 stripe_id(새 열이 이름 변경에 대한 이유이지만,이 쿼리에 감동하지 않는,이 앞에 추가되었다).

특정 순서로 열을 갖는 것은 순전히 관심이 없습니다. 그러나이 질문을하는 이유는 열의 이름을 바꾸고 삭제하면 프로덕션 모드에서 함수를 사용하는 사람에게 실제 문제가 발생할 수 있기 때문에 걱정할 필요가 없습니다.


PostgreSQL 데이터베이스 테이블에서 열 위치어떻게 변경합니까?를 살펴보십시오 . 스택 오버플로에서 도움이 될 수 있지만 대부분 열을 재정렬하지 않는 것이 좋습니다.
joanolo

답변:


16

9.6 및 9.6.1의 가능한 버그

이것은 나에게 완전히 버그처럼 보입니다 ...

왜 그런지 모르겠지만 그 일이 발생했음을 확인할 수 있습니다. 이것은 버전 9.6.0 및 9.6.1에서 문제를 재현하는 가장 간단한 방법입니다.

CREATE TABLE users
(
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL,
    column_that_we_will_drop TEXT
) ;

-- Function that uses the previous table, and that has a CTE
CREATE OR REPLACE FUNCTION __post_users
    (_useremail text) 
RETURNS integer AS
$$
-- Need a CTE to produce the error. A 'constant' one suffices.
WITH something_even_if_useless(a) AS
(
    VALUES (1)
)
UPDATE
    users
SET
    id = id
WHERE 
    -- The CTE needs to be referenced, if the next
    -- condition were not in place, the problem is not reproduced
    EXISTS (SELECT * FROM something_even_if_useless)
    AND email = _useremail
RETURNING
    id
$$
LANGUAGE "sql" ;

이 설정 후 다음 문장은 작동합니다.

SELECT * FROM __post_users('a@b.com');

이 시점에서 하나의 열을 삭제합니다.

ALTER TABLE users 
    DROP COLUMN column_that_we_will_drop ;

이 변경으로 인해 다음 문장에서 오류가 발생합니다.

SELECT * FROM __post_users('a@b.com');

@Andy가 언급 한 것과 동일합니다.

ERROR: table row type and query-specified row type do not match
SQL state: 42804
Detail: Query provides a value for a dropped column at ordinal position 3.
Context: SQL function "__post_users" statement 1
    SELECT * FROM __post_users('a@b.com');

함수를 삭제하고 다시 생성해도 문제가 해결되지 않습니다.
VACUUM FULL (테이블 또는 전체 데이터베이스)로 문제가 해결되지 않습니다.


버그 보고서는 적절한 PostgreSQL 메일 링리스트로 전달되었으며 매우 빠른 응답을 받았습니다 :

HEAD 또는 9.6 분기 팁에서는 이것을 재현 할 수 없습니다. 나는이 패치로 이미 수정되었다고 생각합니다.이 패치는 9.6.1 후에 약간 진행되었습니다.

https://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=f4d865f22

그러나 보고서에 감사드립니다!

톰 레인


버전 9.6.2

2017-03-06부터 버전 9.6.2에서이 동작을 재현 할 수 없음을 확인할 수 있습니다. 즉,이 릴리스에서 버그가 수정 된 것 같습니다.

최신 정보

@Jana의 의견에 따르면 : "버그가 9.6.1에 있고 9.6.2에서 수정되었음을 확인할 수 있습니다.이 수정 사항은 postgres 릴리스 웹 사이트 에도 나와 있습니다 . 열이 삭제 된 테이블에서 INSERT 또는 UPDATE "



4
나는 9.6.0을 사용하고 있으며 @joanolo는 정확하며 그의 방법으로 버그를 재현 할 수 있습니다. Tom이 그것을 재현 할 수 없다면 아마도이 특정 버전의 격리 된 버그 일 것입니다 (그리고 9.6.1이라고 가정합니까?). 이 질문에 대해 언급 한 바와 같이하면 문제가 나타납니다 떨어 테이블의 열을 하고 CTE가 영향을 미치는와 기능을 사용하여 테이블을. 따라서 문제는 재정렬에만 국한된 것이 아니라 내 질문으로 해결하려는 것입니다. 이를 반영하여 제목을 편집하겠습니다.
Andy

버그가 9.6.1에 있고 9.6.2에서 수정되었음을 확인할 수 있습니다. 이 수정 사항은 postgres 릴리스 웹 사이트 에도 나와 있습니다 . 삭제 된 열이있는 테이블에서 INSERT 또는 UPDATE 중에 잘못된 "쿼리가 삭제 된 열에 값을 제공함"오류 수정
Jana

0

나는 당신이 아마 이것을 들어 본 적이 있다는 것을 알고 있습니다.

  • 논리적 순서는 다음과 같은 것들에만 영향을 미칩니다. SELECT *
  • 논리적 순서의 효과는 모양으로 제한됩니다.

경우에 따라서 전혀 mattering하지 당신을 단념하지 않고 우리는 우리가 행 구조로 포토샵을 재생 및 디스플레이 집착하고 있다는 인정의 좀 더 일을 설명 할 수 있습니다.

  • 논리적 순서는 PostgreSQL에 존재하지 않습니다
  • 실제 주문에는 실질적인 이점이 있습니다 (테이블 포장)
  • PostgreSQL은 물리적 순서를 제어하지 않습니다 CREATE TABLE( 중요도 가 훨씬 높음)

따라서 PostgreSQL은 나쁜 표시 계층입니다. 무엇보다도, ALTER잘 작동 하는 동안 용을 유혹해서는 안됩니다. 둘 ALTER TABLE, 그리고 주의해야 섹션 이의 메이크업 언급,

현재의 일부 DDL 명령 TRUNCATE의 테이블 재 작성 형식은 ALTER TABLEMVCC 안전하지 않습니다. 이는 잘림 또는 다시 쓰기 커밋 후 DDL 명령이 커밋되기 전에 스냅 샷을 사용하는 경우 동시 트랜잭션에 대해 테이블이 비어있는 것으로 나타납니다. 이는 DDL 명령이 시작되기 전에 문제의 테이블에 액세스하지 않은 트랜잭션의 경우에만 발생합니다. 그렇게 한 트랜잭션은 최소한 ACCESS SHARE 테이블 잠금을 보유하므로 트랜잭션이 완료 될 때까지 DDL 명령을 차단합니다. 따라서이 명령은 목표 테이블에 대한 연속 쿼리에 대해 테이블 ​​내용에 명백한 불일치가 발생하지 않지만 목표 테이블의 내용과 데이터베이스의 다른 테이블간에 눈에 불일치가 발생할 수 있습니다.

그리고 이것으로 충분하지 않다면, 이것이 여전히 좋은 아이디어라고 생각하고 싶을 것입니다. 그런 다음 시도해보십시오.

  1. 와 함께 테이블을 덤프 pg_dump -t
  2. 덤프에서 열을 손으로 다시 정렬하십시오.
  3. TEMP 테이블에 자료를로드하십시오.
  4. BEGIN 거래
  5. DROP 이전 테이블은
  6. RENAME 임시 테이블을 prod 테이블로
  7. COMMIT

그 소리의 모든 과도한 경우, (그들이있어 가정 데이터베이스의 행을 업데이트하는 행을 재 작성해야합니다 것을 명심 하지 TOAST . 당신은 데이터와 재건 테이블 스키마를 구문 분석하는 데있어,하지만 어느 쪽이든 당신은 재 작성에있어 행. 나는 경우 했다 ,이 작업을 할 수는 내가 그것을 할 것입니다 방법입니다.

그러나이 모든 것이 일반적으로 말하고 있습니다. 아무도 결과를 재현하지 못했습니다.

실행할 수없는 테스트 사례가 있습니다.

ERROR:  column users.authentication_code does not exist
LINE 24: users.authentication_code,

그리고 정확한 버전을 알려주지 않았습니다 .


자세한 설명을 주셔서 감사합니다. 특정 버전을 포함하도록 질문을 편집했습니다.
Andy

4
버그는 버전 9.6.0에서 재현
ypercubeᵀᴹ

0

데이터베이스를 백업하고 복원하여이 버그를 해결했습니다.

헤 로쿠를위한 단계

  • 유지 보수 모드를 켜십시오. heroku maintenance:on
  • 데이터베이스 백업 : heroku pg:backups:capture
  • 데이터베이스 복원 : heroku pg:backups:restore
  • 앱을 다시 시작하십시오. heroku restart
  • 유지 관리 모드를 끕니다. heroku maintenance:off

0

나는이 버그에 부딪쳤다. DB를 완전히 백업 / 복원하지 않으려는 경우 단순히 테이블 복사가 작동한다는 것을 알고 있어야합니다. 그러나 테이블을 복사하는 "마법적인"방법은 없습니다. 나는 그것을 사용하여 그것을했다 :

SELECT * INTO mytable_copy FROM mytable;
ALTER TABLE mytable RENAME TO mytable_backup; -- just in case. you never know
ALTER TABLE mytable_copy RENAME TO mytable;

그 후에도 인덱스, 외래 키 및 기본값을 수동으로 다시 만들어야합니다. 이와 같이 테이블을 다시 만들면 버그가 사라졌습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.