MySQL の暗黙的な変換方法

一个新手
一个新手オリジナル
2017-10-13 10:07:081196ブラウズ

隐式转化规则

官方文档中隐式转化に関する规则は以下の説明の通り:

一方または両方の引数が NULL の場合、NULL セーフ 24ec9cc6e2e36b84b07a2a991c1d2cbf NULL の場合、結果は true になります。変換は必要ありません。

  • 比較演算の両方の引数が文字列の場合、文字列として比較されます。

  • 両方の引数が整数の場合、整数として比較されます。

  • 16 進値は次のように扱われます。数値と比較されない場合はバイナリ文字列。

  • 引数の 1 つが TIMESTAMP または DATETIME 列で、もう 1 つの引数が定数の場合、定数は比較が実行される前にタイムスタンプに変換されます。これは、ODBC との親和性を高めるために行われます。これは IN() の引数に対しては行われないことに注意してください。安全のため、比較を行うときは常に完全な日時、日付、または時刻の文字列を使用してください。たとえば、日付または時刻の値で BETWEEN を使用するときに最良の結果を得るには、CAST() を使用して値を目的のデータ型に明示的に変換します。
    1 つまたは複数のテーブルからの単一行のサブクエリは定数とみなされません。たとえば、サブクエリが DATETIME 値と比較する整数を返す場合、比較は 2 つの整数として行われます。整数は時間値に変換されません。オペランドを DATETIME 値として比較するには、CAST() を使用してサブクエリ値を DATETIME に明示的に変換します。

  • 引数の 1 つが 10 進数値の場合、比較は他の引数に依存します。引数は、他の引数が 10 進数または整数値の場合は 10 進数値として比較され、他の引数が浮動小数点値の場合は浮動小数点値として比較されます。

  • その他すべての場合、引数は浮動小数点数として比較されます。 -ポイント (実数)。

翻译中文就是:

    1. 二つのパラメータが NULL の場合、比較の結果も NULL、例外は 96b4fef55684b9312718d5de63fb7121 を使用します。 对两个 NULL 做比较時会は 1 を返します、この状況况都不必要做型转换

    2. 两个パラメータ都是文字列、会按照字文字列来比较、不做型转换

    3. 二つのパラメータ都は整数、按照整数来比较、不做型转换

    4. 十六进制的值および非数字做比较時、会当為二进制串

    5. 一つありますパラメータは TIMESTAMP または DATETIME で、さらにパラメータが常量であり、常量はタイムスタンプに変換されます

    6. パラメータが 10 進数である場合、パラメータが 10 進数または整数である場合、整数は 10 進数に変換されます後は比較较、追加のパラメータが浮遊量の場合、10 進数の転位が浮遊量として比較されます

    7. その他すべての状況では、2 つのパラメータが浮遊量として再比較されます

问题説明
  • ここで、条件语里、フィールド属性および赋给の条件、当データータイプが不一致である場合、一致转换を行う必要があります
  • 軘认转换规:
    • 異種型全都转换は浮点型(下文都说成整型了,一意思)
    • 如果字段是字符,条件是整型,那么会把表中字段全都转换是整型(也就是上面图)中的问题、下面有详细解释)

转换总结
  1. 字符转整型
  • 字符开头の定数は0

  • 数字开头的、直接取得第一つは文字の位置ではありません

  • 時間の種類转换
    • 按照文字文字串行截取
    • 23:12:13 -> 2023-12-13(後文有讨论)
    • 不符合の時間帯、例10:12:32等、会变は0000-00-00または空
    • cast関数タイムスタンプは、('1970-01-01 00:00:01.000000' から'2038-01-19 03:14:07.999999' )、所以只能は2023年、不可は1923年
    直接截日時字段
    • 直接截日時字段
    • 、直接00:0 0:00
    • 00:00:00を追加

    • 日付を日時またはタイムスタンプに

    • 日付を時刻に

    • 日時またはタイムスタンプを日付に

    • 日時または時刻へのタイムスタンプ

    • 時刻を日時またはタイムスタンプに変換します

    • 時刻と日時を数値に変換すると、倍精度にミリ秒を加えたものになります(バージョンが異なります)

    ケース分析

    • テーブル構造、名前フィールドにはインデックスがあります

    -- 注意name字段是有索引的CREATE TABLE `t3` (  `id` int(11) NOT NULL,  `c1` int(11) NOT NULL,  `name` varchar(100) NOT NULL DEFAULT 'fajlfjalfka',  KEY `name` (`name`),  KEY `id` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin11 row in set (0.00 sec)
    -- 模拟线上一个隐式转换带来的全表扫面慢查询-- 发生隐式转换
    xxxx.test> select * from t3 where name = 0;
    +----+----+-------------+
    | id | c1 | name        |
    +----+----+-------------+
    |  1 |  2 | fajlfjalfka |
    |  2 |  0 | fajlfjalfka |
    |  1 |  2 | fajlfjalfka |
    |  2 |  0 | fajlfjalfka |
    +----+----+-------------+
    4 rows in set, 4 warnings (0.00 sec)-- 上述SQL执行计划是全表扫描,扫描后,字符转整型,都是0,匹配上了条件,全部返回
    xxxx.test> desc select * from t3 where name = 0;
    +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
    +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
    |  1 | SIMPLE      | t3    | ALL  | name          | NULL | NULL    | NULL |    4 | Using where |
    +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
    1 row in set (0.00 sec)-- 加上单引号后,是走name索引的,非全表扫描
    xxxx.test> desc select * from t3 where name = '0';
    +----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra                 |
    +----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
    |  1 | SIMPLE      | t3    | ref  | name          | name | 102     | const |    1 | Using index condition |
    +----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
    1 row in set (0.00 sec)-- 走索引,没返回
    xxxx.test>  select * from t3 where name = '1';
    Empty set (0.00 sec)

    説明

    • 条件が 0 または 1 として書かれている場合、すべての名前フィールドが文字から整数に変換され、0 または 1 と比較される必要があります。これらはすべて文字で始まる文字であるため、すべて 0 に変換され、返される結果はすべての行になります。

    • そこで誰かが、条件内の 0'0' に自動的に変更したらどうだろうかと尋ねました。以下を参照してください。 0 自动改成 '0' ?见下文。

    转换举例

    -- 字符开头,直接是0
    xxxx.test> select cast('a1' as unsigned int) as test ;
    +------+
    | test |
    +------+
    |    0 |
    +------+
    1 row in set, 1 warning (0.00 sec)
    
    xxxx.test> show warnings;
    +---------+------+-----------------------------------------+
    | Level   | Code | Message                                 |
    +---------+------+-----------------------------------------+
    | Warning | 1292 | Truncated incorrect INTEGER value: 'a1' |
    +---------+------+-----------------------------------------+
    1 row in set (0.00 sec)-- 开头不是字符,一直截取到第一个不是字符的位置
    xxxx.test> select cast('1a1' as unsigned int) as test ; 
    +------+
    | test |
    +------+
    |    1 |
    +------+
    1 row in set, 1 warning (0.00 sec)
    
    xxxx.test> select cast('123a1' as unsigned int) as test ;
    +------+
    | test |
    +------+
    |  123 |
    +------+
    1 row in set, 1 warning (0.00 sec)-- 直接按照字符截取,补上了20(不能补19)
    xxxx.test> select cast('23:12:13' as datetime) as test ;
    +---------------------+
    | test                |
    +---------------------+
    | 2023-12-13 00:00:00 |
    +---------------------+
    1 row in set (0.00 sec)-- 为什么不能转换为timestamp,没搞清楚,官方文档给的转换类型里没有timestamp。如果是这样的话,上面的datetime就不好解释为什不是1923了。难道是检测了当前的系统时间?
    xxxx.test> select cast('23:12:13' as timestamp) as test ;    
    ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'timestamp) as test' at line 1-- 这个时间无法转换成datetime
    xxxx.test> select cast('10:12:32' as datetime) as test ;         
    +------+
    | test |
    +------+
    | NULL |
    +------+
    1 row in set, 1 warning (0.00 sec)
    
    xxxx.test> show warnings ;
    +---------+------+--------------------------------------+
    | Level   | Code | Message                              |
    +---------+------+--------------------------------------+
    | Warning | 1292 | Incorrect datetime value: '10:12:32' |
    +---------+------+--------------------------------------+
    1 row in set (0.00 sec)-- 5.5版本下,时间转字符,会增加ms
    xxxx.(none)> select version();
    +------------+
    | version()  |
    +------------+
    | 5.5.31-log |
    +------------+
    1 row in set (0.00 sec)
    
    xxxx.(none)> select CURTIME(), CURTIME()+0, NOW(), NOW()+0 ;
    +-----------+---------------+---------------------+-----------------------+
    | CURTIME() | CURTIME()+0   | NOW()               | NOW()+0               |
    +-----------+---------------+---------------------+-----------------------+
    | 15:40:01  | 154001.000000 | 2016-05-06 15:40:01 | 20160506154001.000000 |
    +-----------+---------------+---------------------+-----------------------+
    1 row in set (0.00 sec)-- 5.6 不会
    xxxx.test> select version();
    +------------+
    | version()  |
    +------------+
    | 5.6.24-log |
    +------------+
    1 row in set (0.00 sec)
    
    xxxx.test> select CURTIME(), CURTIME()+0, NOW(), NOW()+0 ;
    +-----------+-------------+---------------------+----------------+
    | CURTIME() | CURTIME()+0 | NOW()               | NOW()+0        |
    +-----------+-------------+---------------------+----------------+
    | 15:40:55  |      154055 | 2016-05-06 15:40:55 | 20160506154055 |
    +-----------+-------------+---------------------+----------------+
    1 row in set (0.00 sec)

    为什么不把 where name = 0 中的 0 转换为 '0'

    • 変換例

      -- 上面遗留的问题,跟系统时间并没有关系。怀疑虽然指定的是datetime,但是内部还是按照timestamp去做的。
      mysql> select now();
      +---------------------+
      | now()               |
      +---------------------+
      | 1999-08-03 14:16:50 |
      +---------------------+
      1 row in set (0.00 sec)
      
      mysql> select cast('23:12:13' as datetime) as test ;
      +---------------------+
      | test                |
      +---------------------+
      | 2023-12-13 00:00:00 |
      +---------------------+
      1 row in set (0.00 sec)

      where name = 00'0' に変換してみませんか?

    数値を 0 から '0' などの文字に変換すると、クエリの結果はフィールドが '0' に等しいということだけになりますが、実際にはテーブル内のデータ (' など) a0'、'00'、これらは実際にはユーザーが望む 0 です。結局のところ、ユーザーは数値 0 を指定したので、MySQL は依然としてユーザーのリクエストを標準として受け取ります。それ以外の場合、'00' はユーザーに返されません。 。
    • まとめ
    • 上記の内容で、最初の質問は説明できます。

    上の図の例を使用して認証をバイパスできますか?


    🎜サプリメント🎜rrreee🎜🎜🎜

    以上がMySQL の暗黙的な変換方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。