为Mysql / Mariadb创建唯一索引的Ecto失败

时间:2015-09-08 22:19:49

标签: mysql mariadb elixir ecto

我尝试进行以下迁移:

defmodule Shopper.Repo.Migrations.MakeNameUniqueShopper do
  use Ecto.Migration

  def change do
    create unique_index :shoppers, [:name]
  end
end

还尝试了create unique_index :shoppers, [:name], name: :name_uniquecreate unique_index :shoppers, [:name], name: "name_unique"create index(:shoppers, [:name], unique: true)

但他们失败了类似的错误:

[info]  == Running Shopper.Repo.Migrations.MakeNameUniqueShopper.change/0 forward

[info]  create index shoppers_name_index
** (Mariaex.Error) (1071): Specified key was too long; max key length is 767 bytes
    (ecto) lib/ecto/adapters/sql.ex:172: Ecto.Adapters.SQL.query!/5
    (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
...
...

非常感谢任何帮助,以帮助我解决错误。

注意:我使用的是ecto 1.02

以下是使用mix phoenix.gen.model

创建的第一次迁移
defmodule Shopper.Repo.Migrations.CreateV1.Shopper do
  use Ecto.Migration

  def change do
    create table(:shoppers) do
      add :name, :string
      add :oauth_token, :string

      timestamps
    end
  end
end

信息name字段为utf8mb4,由我的架构指定

更新:我知道解决方案是减少name字段长度,但是如何使其适用于phoenix模型和迁移?因为它需要一个字符串?

3 个答案:

答案 0 :(得分:1)

感谢JoséValim帮助我完成他的回答,虽然这个答案是我问题的确切解决方案。

使用以下代码创建新的ecto迁移脚本:

defmodule Shopper.Repo.Migrations.MakeNameUniqueShopper do
  use Ecto.Migration

  def change do
    alter table(:shoppers) do
      modify :name, :string, size: 100
    end

    create unique_index :shoppers, [:name], name: :shopper_name_unique
  end
end

答案 1 :(得分:1)

这里的问题是InnoDB的密钥大小(767字节),它直接映射到相对于列的字符集的varchar()列的可能大小。如果您使用charset utf8,则varchar列最多可存储255个字符。如果您使用utf8mb4,则varchar列只能存储191个字符。

此博客文章详细介绍了https://mathiasbynens.be/notes/mysql-utf8mb4

将此规则应用于索引时,就像josé建议的那样,肯定是一种可能性,我会说你最好修复你的varchar列大小并且CREATE INDEX首先没有抱怨并且有一个正确的表列布局:

defmodule Shopper.Repo.Migrations.CreateV1.Shopper do
  use Ecto.Migration

  def change do
    create table(:shoppers) do
      add :name, :varchar, size: 191
      add :oauth_token, :varchar, size: 191

      timestamps
    end
    create unique_index(:shoppers, [:name])
  end
end
  

关注add :name, :string, size: 191无效,因为ecto会将:string类型直接映射到varchar(255) in the mysql adapter

我的观点是,在将:string映射到其原生类型just like rails does it时,ecto的mysql适配器应该考虑字符集。

答案 2 :(得分:0)

使用utf8mb4编码创建更短的varchar / text列大小的替代方法是配置MySQL以将最大InnoDB索引前缀大小增加到3072字节。

defmodule Shopper.Repo.Migrations.CreateV1.Shopper do
  use Ecto.Migration

  def change do
    # just needs to be done once
    execute "SET GLOBAL innodb_file_format = BARRACUDA"
    execute "SET GLOBAL innodb_file_per_table = ON"
    execute "SET GLOBAL innodb_large_prefix = ON"

    # in MySQL 5.7.9 or higher, this sets the default row format
    # otherwise for all new tables you create, you must manually 
    # alter row_format to dynamic before adding any string/text columns
    execute "SET GLOBAL innodb_default_row_format = DYNAMIC"

    # change existing shoppers row format to dynamic
    execute "ALTER TABLE shoppers ROW_FORMAT = DYNAMIC"

    create unique_index :shoppers, [:name], name: :shopper_name_unique         
  end
end