我正在尝试在 PostgreSQL 中实现分区。问题是一个分区表引用了另一分区表。还有第三个常规表也引用分区表。问题恰恰出在对分区表的引用上。
我每次尝试创建 FK 都会导致如下错误:
there is no unique constraint matching given keys for referenced table "units"
此错误是由于
lessons
表的迁移而发生的。
表格列有简单明了的名称:
id
、unit_id
等:
t.belongs_to :unit, type: :uuid, null: false, foreign_key: true
我可以使用此代码创建一个列:
t.belongs_to :unit, type: :uuid, null: false
但结果,不会创建外键。
您能详细告诉我需要做什么吗?
UPD
迁移示例:
create_range_partition :units, id: :uuid, partition_key: :created_at, template: false do |t|
t.belongs_to :course, type: :uuid, null: false, foreign_key: true
# ...
t.timestamps
end
create_range_partition :lessons, id: :uuid, partition_key: :created_at, template: false do |t|
t.belongs_to :unit, type: :uuid, null: false, foreign_key: true # <= THIS WILL CAUSE AN ERROR
# t.belongs_to :unit, type: :uuid, null: false # <= THIS WILL BE CREATED
# ...
t.timestamps
end
避免使用分区表,除非使用它们可以带来令人信服且明显的好处(例如,需要定期清除大量时间分区数据)。使用分区表会带来复杂性和限制。
PostgreSQL 17 第 5.12.2.3 节中的第一个项目符号项目。限制状态(强调我的):
要在分区表上创建唯一键或主键约束,分区键不得包含任何表达式或函数调用,并且将分区键作为分区表唯一索引的一部分的要求意味着引用分区表的外键约束还必须包含对这些列的引用。在OP的具体情况下,从约束的列必须包含所有分区键列。存在这种限制是因为构成约束的各个索引只能直接在自己的分区内强制执行唯一性;因此,分区结构本身必须保证不同分区之间不存在重复。
lessons
到
units
的外键约束必须引用
units(id, created_at)
。以下是设置迁移和类以在 Ruby on Rails 应用程序中使用 PostgreSQL 分区表的示例代码(此代码取决于
pg_party gem)。
# db/migrate/20241103010658_create_courses.rb
class CreateCourses < ActiveRecord::Migration[7.1]
def change
create_table :courses, id: :uuid do |t|
t.string :title
t.timestamps
end
end
end
# app/models/course.rb
class Course < ApplicationRecord
has_many :units
end
# db/migrate/20241103010710_create_units.rb
class CreateUnits < ActiveRecord::Migration[7.1]
def up
create_range_partition :units, id: :uuid, partition_key: :created_at, template: false do |t|
t.uuid :course_id, null: false
t.string :title
t.timestamps
end
add_index :units, [:id, :created_at], unique: true
execute "ALTER TABLE units ADD FOREIGN KEY (course_id) REFERENCES courses(id)"
create_range_partition_of :units, name: :units_2024, start_range: "2024-01-01", end_range: "2025-01-01"
end
def down
execute "DROP TABLE units"
end
end
# app/models/unit.rb
class Unit < ApplicationRecord
self.primary_key = [:id, :created_at]
has_many :lessons
end
# db/migrate/20241103010717_create_lessons.rb
class CreateLessons < ActiveRecord::Migration[7.1]
def up
create_range_partition :lessons, id: :uuid, partition_key: :created_at, template: false do |t|
t.uuid :unit_id
t.timestamp :unit_created_at
t.string :title
t.timestamps
end
add_index :lessons, [:id, :created_at], unique: true
execute "ALTER TABLE lessons ADD FOREIGN KEY (unit_id, unit_created_at) REFERENCES units(id, created_at)"
create_range_partition_of :lessons, name: :lessons_2024, start_range: "2024-01-01", end_range: "2025-01-01"
end
def down
execute "DROP TABLE lessons"
end
end
# app/models/lesson.rb
class Lesson < ApplicationRecord
self.primary_key = [:id, :created_at]
belongs_to :unit, query_constraints: [:unit_id, :unit_created_at]
end
ActiveRecord 对复合外键和分区表的支持是有限的。下面演示了与 ActiveRecord 处理复合主键相关的工件之一,其中 u
是上面定义的
Unit
类型的实例:
irb(main):005> u
=>
#<Unit:0x0000000127166098
id: "864f835d-ff27-4cf5-bdd7-fdc6e065a529",
course_id: "451c8e20-e119-4a5b-a22b-97ffaf2a3e1a",
title: "First Unit",
created_at: Sun, 03 Nov 2024 07:01:14.202998000 UTC +00:00,
updated_at: Sun, 03 Nov 2024 07:01:14.202998000 UTC +00:00>
irb(main):006> u.id
=> ["864f835d-ff27-4cf5-bdd7-fdc6e065a529", Sun, 03 Nov 2024 07:01:14.202998000 UTC +00:00]
irb(main):007> u["id"]
=> "864f835d-ff27-4cf5-bdd7-fdc6e065a529"
irb(main):008> u[:id]
=> "864f835d-ff27-4cf5-bdd7-fdc6e065a529"
请注意,u["id"]
和
u[:id]
返回相同的值,但
u.id
返回表示复合主键的数组。这很容易导致代码中出现细微的缺陷。