Ecto 创建唯一索引失败 Mysql/Mariadb
Ecto creating unique index failed for 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
Info: name
字段是utf8mb4,由我的schema指定
更新:我知道解决方案是减少name
字段长度,但如何让它与凤凰模型和迁移一起工作?正如它所期望的那样?
字段 "name" 太长。您应该通过在声明它时传递大小选项来确保它的大小小于 767 字节,或者仅对字段的一部分进行索引:
create unique_index :shoppers, ["name(20)"], name: :shoppers_name_unique
请记住,在您的变更集中调用 unique_constraint/2
时需要提供相同的名称。
感谢 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
使用 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
这里的问题是 InnoDB 的键大小(767 字节),它直接映射到 varchar()
列的可能大小(相对于列的字符集)。如果您使用字符集 utf8
,一个 varchar
列最多可以存储 255 个字符。如果您使用 utf8mb4
,一个 varchar
列只能存储 191 个字符。
此博客 post 详细介绍:https://mathiasbynens.be/notes/mysql-utf8mb4
虽然像 josé 建议的那样将此规则应用于索引,但肯定是一种可能性,我会说你最好修复你的 varchar 列大小并且让 CREATE INDEX
首先不要抱怨并有一个适当的table 栏目布局:
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
attention: add :name, :string, size: 191
will not work as ecto maps the :string
type directly to varchar(255)
in the mysql adapter
我的意见是 ecto 的 mysql 适配器在将 :string
映射到其原生类型 just like rails does it.
时应该考虑字符集
我尝试进行以下迁移:
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
Info: name
字段是utf8mb4,由我的schema指定
更新:我知道解决方案是减少name
字段长度,但如何让它与凤凰模型和迁移一起工作?正如它所期望的那样?
字段 "name" 太长。您应该通过在声明它时传递大小选项来确保它的大小小于 767 字节,或者仅对字段的一部分进行索引:
create unique_index :shoppers, ["name(20)"], name: :shoppers_name_unique
请记住,在您的变更集中调用 unique_constraint/2
时需要提供相同的名称。
感谢 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
使用 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
这里的问题是 InnoDB 的键大小(767 字节),它直接映射到 varchar()
列的可能大小(相对于列的字符集)。如果您使用字符集 utf8
,一个 varchar
列最多可以存储 255 个字符。如果您使用 utf8mb4
,一个 varchar
列只能存储 191 个字符。
此博客 post 详细介绍:https://mathiasbynens.be/notes/mysql-utf8mb4
虽然像 josé 建议的那样将此规则应用于索引,但肯定是一种可能性,我会说你最好修复你的 varchar 列大小并且让 CREATE INDEX
首先不要抱怨并有一个适当的table 栏目布局:
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
attention:
add :name, :string, size: 191
will not work as ecto maps the:string
type directly tovarchar(255)
in the mysql adapter
我的意见是 ecto 的 mysql 适配器在将 :string
映射到其原生类型 just like rails does it.