【问题标题】:Case sensitive uniqueness and case insensitive search区分大小写的唯一性和不区分大小写的搜索
【发布时间】:2012-01-31 21:52:50
【问题描述】:

我有一个使用编码 utf8 和排序规则 utf8_unicode_ci 的字段 a 的表:

CREATE TABLE dictionary (
    a varchar(128) NOT NULL
) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

排序规则 utf8_unicode_ci 是高效的不区分大小写搜索所必需的,带有扩展和连接。为此,我有索引:

CREATE INDEX a_idx on dictionary(a);

问题:此外,我必须确保字段 a 的所有存储值都是唯一的,但以 区分大小写的方式。 德语示例:“blühen”和“Blühen”必须都存储在表中。但是第二次添加“Blühen”应该是不可能的。

MySQL 中是否有内置功能可以同时具备这两种功能?

不幸的是,在 MySQL 5.1 中似乎无法为索引设置排序规则。

此问题的解决方案包括插入前的唯一性检查或触发器。两者都远没有使用唯一索引那么优雅。

【问题讨论】:

  • 不幸的是,MySQL 缺少其他 RDBMS 具有的索引/物化视图或计算列或基于函数的索引等功能。我当然有兴趣看看它是如何完成的......
  • 添加另一个具有区分大小写的排序规则和唯一性约束的列对您有用吗?
  • 我觉得这个话题很有帮助。 stackoverflow.com/questions/4945349/…
  • 感谢您的 cmets!添加另一列将是一种选择。对于我的特定目的,我认为在任何 few 更新或插入操作之前添加唯一性检查是最好的选择。我原来的帖子是一个简化。其实我的列多,读操作多,又怕加多列会因为内存有限而成为性能问题。

标签: mysql collation


【解决方案1】:

嗯,有两种方法可以做到这一点:

  1. 使用 _bin 排序规则
  2. 将数据类型更改为 VARBINARY

案例 1:使用 _bin 排序规则

如下创建表格:

CREATE TABLE `dictionary` (
 `a` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
 UNIQUE KEY `idx_un_a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

请注意:

  1. a的数据类型
  2. a上的唯一索引

案例 2:使用 VARBINARY 数据类型

如下创建表格:

CREATE TABLE `dictionary` (
 `a` VARBINARY(128) NOT NULL,
 UNIQUE KEY `idx_uniq_a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

请注意:

  1. 新的数据类型 VARBINARY
  2. a上的唯一索引

因此,以上两种方法都可以解决您的目的。也就是说,它们都将允许 'abc'、'Abc'、'ABC'、'aBc' 等值,但如果大小写匹配,则不允许再次使用相同的值。

请注意,提供“_bin”排序规则与使用二进制数据类型不同。因此,请随时参考以下链接:

  1. The BINARY and VARBINARY datatypes
  2. The _bin and binary Collations

希望以上内容对你有帮助!

【讨论】:

  • 感谢您的回复!我看不出如何使用此解决方案进行有效的 (O(log(n)) 和不区分大小写的搜索。
  • @user1091141,当然您可以通过更改排序规则进行不区分大小写的搜索,例如像SELECT * FROM dictionary WHERE a COLLATE utf8_general_ci = 'abc' 这样的查询。抱歉,如果我的回答不清楚,但我猜你可以弄清楚,这是一个链接 - “dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html”。关于 O(log(n)),我很抱歉,但我的数学不是那么强,但我不明白为什么搜索会效率低下。或者,您可以保留 2 个 a 列 - 一个具有通用排序规则,用于不区分大小写的搜索,另一个具有 _bin 用于区分大小写的插入
  • 如果我在 where 子句中指定的排序规则与表定义中定义的排序规则不同,MySQL 将不使用索引,而是进行全表扫描。对于大表,全表扫描可能需要很长时间,这就是为什么它们被认为是低效的。执行EXPLAIN SELECT * FROM dictionary WHERE a COLLATE utf8_general_ci = 'abc' 表示已读取表中的所有行。这至少适用于我的 MySQL 版本(5.0 和 5.1)。要是不一样就好了。
  • 我还可以想到另外两件事。一种是您保留原始表结构(没有排序规则)并使用 BEFORE INSERT 触发器防止区分大小写的插入。其次,您可以将我对“a”的定义作为单独的列添加到原始表中
  • 我同意这两种解决方案。不幸的是,我无法编写所需的触发器,因为我没有找到如何在 MySQL 5.1 中使用触发器引发错误消息。这怎么可能?我更喜欢触发器解决方案,因为我注意到添加更多列时性能会显着下降。
【解决方案2】:

您可以通过添加附加列“column_lower”来实现此目的。

CREATE TABLE `dictionary` (
  `a` VARCHAR(128) NOT NULL,
  `a_lower` VARCHAR(128) NOT NULL,
  UNIQUE KEY `idx_un_a_lower` (`a_lower`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

插入如下:

insert into dictionary set a = x, a_lower = lower(x);

Select 现在可以不区分大小写了:

select * from dictionary where a_lower like lower('search_term%')

请注意,上面有索引的列最多可以存储 191 个字符。 MySQL 最多可以有 767 个字节的长索引,即 767 / 4(如果使用 utf8mb4 排序规则,unicode 最多可以占用 4 个字节)= 191.75 = 191 个字符。如果您使用 utf8 排序规则,每个字符列最多占用 3 个字节,则最多可以存储 767 / 3 = 255 个字符。

【讨论】:

    【解决方案3】:
    SELECT * FROM dictionary WHERE a COLLATE utf8_general_ci = 'abc'
    

    试试这个它会工作..它对我有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-03
      • 2017-12-01
      • 2010-09-15
      • 2013-06-15
      • 1970-01-01
      • 2013-04-17
      相关资源
      最近更新 更多