Ecto не удалось создать уникальный индекс для Mysql/Mariadb

Я пытаюсь выполнить следующую миграцию:

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_unique, create 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 и миграцией? Как он ожидает строку?


person ardhitama    schedule 08.09.2015    source источник
comment
Укажите SHOW CREATE TABLE, чтобы мы могли обсудить детали.   -  person Rick James    schedule 09.09.2015
comment
@RickJames Я не уверен, что это уместно здесь. Его создал mix phoenix.gen.model   -  person ardhitama    schedule 09.09.2015
comment
Как мы можем определить поля, которые он использует в ключе, не видя структуры таблицы? Это то, о чем просит @RickJames (если я правильно понимаю).   -  person Onorio Catenacci    schedule 09.09.2015
comment
добавил скрипт создания   -  person ardhitama    schedule 09.09.2015


Ответы (4)


Поле "имя" слишком длинное. Вы должны либо убедиться, что его размер меньше 767 байт, передав параметр размера при его объявлении, либо проиндексировать только часть поля:

create unique_index :shoppers, ["name(20)"], name: :shoppers_name_unique

Имейте в виду, что вам нужно будет указать то же имя при вызове unique_constraint/2 в вашем наборе изменений.

person José Valim    schedule 09.09.2015
comment
Я пропустил пункт о том, что поле должно быть меньше 767 байт, а строка в add :name, :string на самом деле varchar(255). Но ["name(20)"] не является правильным именем поля, поэтому приводит к другой ошибке - person ardhitama; 09.09.2015
comment
Я думаю, что правильным ответом было бы перенести его сначала на name(20), а затем на create unique_index :shoppers, [:name], name: :shopper_name_unique. - person ardhitama; 09.09.2015
comment
и... моя схема utf8mb4 - person ardhitama; 09.09.2015
comment
но... у ecto, кажется, нет способа установить name как varchar(20), так как он ожидает string в эликсире? - person ardhitama; 09.09.2015
comment
Вот синтаксис для добавления поля с размером: add :name, :string, size: 100 - person José Valim; 09.09.2015
comment
Если вы получите UNIQUE name(20), это будет не то, что вы хотите. Это ограничивает уникальность только первых 20 символов name. Эти названия городов будут рассматриваться как идентичные: Cortijada El Barranco de Amatisteros, Cortijada El Barranco de las Minas, Cortijada El Barranco de los Lobos. - person Rick James; 10.09.2015

Спасибо Хосе Валиму за помощь в его ответе, хотя этот ответ является точным решением моей проблемы.

Создайте новый скрипт экто-миграции со следующим кодом:

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
person ardhitama    schedule 09.09.2015

проблема здесь заключается в размере ключа InnoDB (767 байт), который напрямую соответствует возможному размеру столбцов varchar() по отношению к кодировке столбца. если вы используете кодировку utf8, столбец varchar может хранить максимум 255 символов. если вы используете utf8mb4, столбец varchar может хранить только 191 символ.

этот пост в блоге содержит подробные сведения: https://mathiasbynens.be/notes/mysql-utf8mb4

в то время как применение этого правила к индексу, как предлагает Хосе, безусловно, является одной из возможностей, я бы сказал, что вам лучше исправить размеры столбцов 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) в адаптере mysql

я считаю, что адаптер mysql ecto должен учитывать кодировку при сопоставлении :string с его собственным типом , как и рельсы делает это.

person glasz    schedule 03.08.2016

Альтернативой созданию более коротких столбцов varchar/text с кодировкой utf8mb4 является настройка 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
person seanomlor    schedule 02.06.2016