如何在数据库迁移中使用外键迁移。 (错误号:150 "Foreign key constraint is incorrectly formed")?

How to use foreign keys migration with database migrations. (errno: 150 "Foreign key constraint is incorrectly formed")?

CodeIgniter 4 有一个方便的迁移和播种器解决方案。不使用外键,一切都运行良好。但是当我使用外键时,我得到“无法添加外键”。

这是因为发生的顺序:

快速示例:

table bar
 -------------------
| id | name | fooid |
FOREIGN KEY fooid REFERENCES foo.id

table foo
 ----------
| id | name|

所以当我现在 运行 php spark migratephp spark migrate:refresh 时无法设置外键,因为 bar table 已创建,但引用foo table 还不存在。

从技术上讲,我可以 运行 在迁移后 运行 一个单独的函数,但我喜欢 php spark migrate 的一个命令,一切都完成了。

解决这个问题的正确方法是什么?

这是我创建table创建的代码:

CREATE TABLE `bar` (
    `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    `fooid` int(11) UNSIGNED NOT NULL,
    CONSTRAINT `pk_bar` PRIMARY KEY(`id`),
    CONSTRAINT `bar_fooid_foreign` FOREIGN KEY(`fooid`) REFERENCES `foo` (`id`) ON DELETE SET NULL,
    KEY `fooid` (`fooid`)
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_general_ci;

CREATE TABLE `foo` (
    `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    CONSTRAINT `pk_foo` PRIMARY KEY(`id`),
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_general_ci;

附录

觉得我找到问题的方向了。 从评论中注意到,它是正确的,tables 的创建顺序不正确。这可以通过更改文件名中的时间戳来解决,以便 foobar 之前创建。但这并不能解决问题,我发现了其他东西: 这是我用来迁移 bar:

的代码
class Bar extends Migration
{
    public function up()
    {
        $fields = [
            'id' => [
                'type' => 'int',
                'constraint' => 11,
                'unsigned' => true,
                'auto_increment' => true,
            ],
            'name' => [
                'type' => 'varchar',
                'constraint' => 255,
                'default' => null,
            ],
            'fooid' => [
                'type' => 'int',
                'constraint' => 11,
                'unsigned' => true,
            ],
        ];
        $this->forge->addField($fields);

        $this->forge->addPrimaryKey('id');
        $this->forge->addKey('fooid');

        $this->forge->addForeignKey('fooid', 'foo', 'id', '', 'SET NULL');

        $this->forge->createTable('bar');
    }

    public function down()
    {
        $this->forge->dropTable('bar');
    }
}

这会生成一个 fooid int(11) UNSIGNED NOT NULL。问题是,foodid 不能是 null,但 fk 在删除时将值设置为 null

但是...字段 null 的默认值是 'null' => true,即使我手动添加它,它也不会生成可为 null 的字段。

只需 重命名 您的迁移时间戳前缀,这样 table foo 的创建先于 bar 的创建。例如迁移文件名如下:

app/Database/Migrations/2022-02-16-101819_CreateBarMigration.php
app/Database/Migrations/2022-04-22-101819_CreateFooMigration.php ❌

重命名他们的迁移时间戳前缀,因为引用的 table (foo) 首先出现。

app/Database/Migrations/2022-04-22-101819_CreateBarMigration.php
app/Database/Migrations/2022-02-16-101819_CreateFooMigration.php ✅

最后,重新运行挂起的迁移。 php spark migrate.


附录 1

参考您新编辑的问题描述,将 foo 的 table 创建查询移动到 bar 的创建查询之前。即:


CREATE TABLE `foo` ( ...)

CREATE TABLE `bar` ( ...)


附录 2

2022-02-16-101819_CreateFooMigration.php

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreateFooMigration extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id'          => [
                'type'           => 'INT',
                'constraint'     => 11,
                'unsigned'       => true,
                'auto_increment' => true,
            ],
            'name'       => [
                'type'       => 'VARCHAR',
                'constraint' => '100',
            ]
        ]);
        $this->forge->addKey('id', true);
        $this->forge->createTable('foo');
    }

    public function down()
    {
        $this->forge->dropTable('foo');
    }
}

2022-04-22-101819_CreateBarMigration.php

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreateBarMigration extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id'          => [
                'type'           => 'INT',
                'constraint'     => 11,
                'unsigned'       => true,
                'auto_increment' => true,
            ],
            'name'       => [
                'type'       => 'VARCHAR',
                'constraint' => '100',
            ],
            'foo_id' => [
                'type' => 'INT',
                'constraint'     => 11,
                'unsigned'       => true,
                'null' => true,
            ],
        ]);
        $this->forge->addKey('id', true);
        $this->forge->addForeignKey('foo_id', 'foo', 'id', 'CASCADE', 'RESTRICT' );
        $this->forge->createTable('bar');
    }

    public function down()
    {
        $this->forge->dropTable('bar');
    }
}