微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

PostgreSQL允许不存在的外键

如何解决PostgreSQL允许不存在的外键

我有一个使用postgresql 12的Rails应用程序。最近,我编写了一些测试并看到了一些奇怪的行为。 我有一个countries表。其模式如下所示:

qq2_test=# \d countries;
                                               Table "public.countries"
        Column         |              Type              | Collation | Nullable |                Default                
-----------------------+--------------------------------+-----------+----------+---------------------------------------
 id                    | bigint                         |           | not null | nextval('countries_id_seq'::regclass)
 domain                | character varying              |           | not null | ''::character varying
 root_city_id          | bigint                         |           |          | 
 language_id           | bigint                         |           | not null | 
 currency_id           | bigint                         |           | not null | 
 google_tag_manager_id | character varying              |           | not null | ''::character varying
 created_at            | timestamp(6) without time zone |           | not null | 
 updated_at            | timestamp(6) without time zone |           | not null | 
Indexes:
    "countries_pkey" PRIMARY KEY,btree (id)
    "index_countries_on_domain" UNIQUE,btree (domain)
    "index_countries_on_currency_id" btree (currency_id)
    "index_countries_on_language_id" btree (language_id)
Foreign-key constraints:
    "fk_rails_6f479b409c" FOREIGN KEY (language_id) REFERENCES languages(id)
    "fk_rails_7cac1212c7" FOREIGN KEY (currency_id) REFERENCES currencies(id)
    "fk_rails_beac36a0bd" FOREIGN KEY (root_city_id) REFERENCES cities(id)
Referenced by:
    TABLE "country_translations" CONSTRAINT "fk_rails_0c4ee35f26" FOREIGN KEY (country_id) REFERENCES countries(id) ON DELETE CASCADE
    TABLE "countries_languages" CONSTRAINT "fk_rails_556e7398aa" FOREIGN KEY (country_id) REFERENCES countries(id) ON DELETE CASCADE
    TABLE "cities" CONSTRAINT "fk_rails_996e05be41" FOREIGN KEY (country_id) REFERENCES countries(id)

如您所见,我在currency_idlanguage_id字段上都具有外键约束。 运行测试时,我看到该表中有记录:

qq2_test=# select * from countries;
    id     | domain | root_city_id | language_id | currency_id | google_tag_manager_id |         created_at         |         updated_at         
-----------+--------+--------------+-------------+-------------+-----------------------+----------------------------+----------------------------
 665664142 | com    |              |  1019186233 |   432072940 |                       | 2020-10-23 06:20:49.288637 | 2020-10-23 06:20:49.288637
 169150333 | by     |              |  1019186233 |   432072940 |                       | 2020-10-23 06:20:49.288637 | 2020-10-23 06:20:49.288637
(2 rows)

我的测试记录有两个,它们具有语言和货币参考。但是他们的表是空的:

qq2_test=# select * from currencies;
 id | name | symbol | created_at | updated_at 
----+------+--------+------------+------------
(0 rows)

qq2_test=# select * from languages;
 id | name | locale | image | created_at | updated_at 
----+------+--------+-------+------------+------------
(0 rows)

为什么Postgresql允许countries表中不存在引用?

  • Ruby 2.7.1 MRI
  • 路轨:6.0.3.4
  • Postgresql 12.4
  • Ubuntu 20.04

解决方法

只有两个选项:

  1. 外键约束为NOT VALID

    为新条目限制了此类约束,但是现有条目会违反它们。

    但这会显示在您的\d输出中,事实并非如此。

  2. 您的数据已损坏。

    除了硬件问题或软件错误外,可能的解释是:

    • 有人设置了session_replication_role = replica,以使触发器不会触发。

    • 超级用户已运行

      ALTER TABLE countries DISABLE TRIGGER ALL;
      
,

假设您是唯一使用数据库的人(因为您正在谈论的是小型的1-2行测试),那么我猜您的Rails应用程序(或相应的驱动程序)正在禁用触发器或外键检查。完全有可能像这样绕过外键检查:

edb=# show session_replication_role ;
 session_replication_role 
--------------------------
 origin
(1 row)

edb=# create table city (id int primary key,name text);
CREATE TABLE
edb=# select * from city;
 id | name 
----+------
(0 rows)

edb=# create table person (id int,name text,city int references city(id));
CREATE TABLE
edb=# insert into person values (1,'foo',1);
ERROR:  insert or update on table "person" violates foreign key constraint "person_city_fkey"
DETAIL:  Key (city)=(1) is not present in table "city".
edb=# set session_replication_role to replica;
SET
edb=# insert into person values (1,1);
INSERT 0 1
edb=# select * from person;
 id | name | city 
----+------+------
  1 | foo  |    1
(1 row)

edb=# select * from city;
 id | name 
----+------
(0 rows)

我建议您暂时设置log_statement = all并再次运行测试-然后在Postgres服务器日志中查看(对于Ubuntu,默认值应为/var/log/postgresql/postgresql-12-main.log),这可能会禁用外键约束检查,然后相应地解决您的发现。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。