mysql - 为什么结束括号被认为等于ö(o umlaut)?

时间:2017-06-22 15:07:58

标签: mysql utf-8 character-encoding

我发现了比较mysql中的字符的一种非常奇怪的行为。

最简单的重现功能是:

set names utf8 collate utf8_general_ci;
drop function if exists contains_bracket;
delimiter ;;
CREATE DEFINER=`db`@`%` FUNCTION `contains_bracket`(str varchar(255) CHARSET utf8) RETURNS varchar(255) CHARSET utf8
  begin
    declare i, result int;
    declare letter varchar(1);
    set result = 0;
    set i = 1;
    set str = lower(str);
    while i <= length(str) do
      set letter = substring(str, i, 1);
      if letter = ']' then
        set result = 1;
      end if;
      set i = i + 1;
    end while;
    return result;
  end;;
delimiter ;

如果参数包含右括号],则函数应返回1,否则返回0。奇怪的是,在这个函数中,变音符ö被认为等于]

像这样测试:

select contains_bracket('[a]'), contains_bracket('abc'), contains_bracket('äöü'), contains_bracket('ö')

将给出

-------------------------------
| '[a]' | 'abc' | 'äöü' | 'ö' |
-------------------------------
|  1    |  0    |  1    |  1  |
-------------------------------

这里发生了什么?谁能解释一下?在使用']' = 'ö'时,utf8_general_ci是真的是mysql中的错误还是我缺少某些内容?

修改

连接字符集和排序规则非常重要,因为存储的函数和过程会保留在创建过程中生效的字符集和排序规则。

请记住,在phpmyadmin中,默认情况下,utf8中的数据交换。连接collat​​iom不会改变这一点。例如,当连接排序规则是latin1,并且我们在查询中的字符串文字中发送非ascii字符时,它们的值将被破坏(例如,当我们键入'ä'(utf8)时,服务器将看到{{1} })

2 个答案:

答案 0 :(得分:2)

这真的 看起来是一个不匹配字符集的问题。

请修正您的declare letter varchar(1);

应为declare letter varchar(1) CHARSET utf8;

发生了什么?

在此作业中

set letter = substring(str, i, 1);

substring结果正在转换为latin1,因为letter默认情况下使用charset latin1声明为varchar(1)

因此,在比较if letter = ']'中,我们左侧的ö位于latin1,右侧位于]

为什么他们被认为是平等的?

MySQL使用二进制表示来比较字符串。 v.5.6及以上版本中有一个函数WEIGHT_STRING()

  

此函数返回输入字符串的权重字符串。该   返回值是表示比较和的二进制字符串   排序字符串的值。

让我们看看WEIGHT_STRING(letter)

set names utf8 collate utf8_general_ci;
drop function if exists contains_bracket;
delimiter ;;
CREATE FUNCTION `contains_bracket`(str varchar(255) CHARSET utf8) RETURNS varchar(255)
  begin
    declare i int;
    declare result varchar(255);
    declare letter varchar(1);
    set result = '';
    set i = 1;
    set str = lower(str);
    while i <= length(str) do
      set letter = substring(str, i, 1);
      if letter = ']' then
        set result = concat(weight_string(letter), ' = ', letter);
        set i = length(str); -- exit the loop 
      end if;
      set i = i + 1;
    end while;
    return result;
  end;;
delimiter ;

测试:

select contains_bracket('[a]'), contains_bracket('abc'), contains_bracket('äöü'), contains_bracket('ö');

将给出

---------------------------------
| '[a]' | 'abc' | 'äöü' |  'ö'  |
---------------------------------
| ] = ] |       | ] = ö | ] = ö |
---------------------------------

letter声明中使用正确的字符集可以解决此问题。

更简单的方法来确定字符串是否包含另一个字符串:

select if(locate(']', '[a]'), 1, 0); -- returns 1
select if(locate(']', 'äöü'), 1, 0); -- returns 0

答案 1 :(得分:1)

这会短得多:

CREATE FUNCTION `contains_bracket`(str varchar(255) CHARSET utf8)
       RETURNS varchar(255) CHARSET utf8
  RETURN str LIKE '%]%';
  end;;

为什么不使用它?

好的,假设真正的任务不允许使用LIKE ...

有一个错误:使用CHAR_LENGTH(),而不是LENGTH()

好的,这不会改变我得到的结果。但是我得到了

mysql> select contains_bracket('[a]'), contains_bracket('abc'), contains_bracket('äöü'), contains_bracket('ö')\G
*************************** 1. row ***************************
   contains_bracket('[a]'): 1
   contains_bracket('abc'): 0
   contains_bracket('äöü'): 0
     contains_bracket('ö'): 0

所以,我必须说&#34;适合我&#34;。

也许my.cnf中有其他一些设置是不对的?您使用的是哪个版本的MySQL?

嗯,我认为以下是真正的答案,因为我在德国键盘上看过它。键盘显示ö,但传输的代码为]。 (好像我曾在斯图加特的80年代任职,并且必须使用德国终端代码C。)

建议您执行以下操作 - 使用SELECT HEX(...)对存储过程进行加密,以确定您实际上正在搜索]