MySQL暗黙型変換によるデータの不一致

9026 ワード

1.1問題の説明
テーブルにSQLを関連付けると、実行効率が悪くなります.原因関連フィールドのデータ型が一致しないため、暗黙的なタイプ変換が発生し、インデックスが失効したことがわかりました.convert変換を使用すると、クエリーの結果が変更されたことがわかります.
テーブル構造
###t1###

CREATE TABLE `t1` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int NOT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `test_json` json NOT NULL,
  `num_dec` decimal(10,0) NOT NULL,
  `num_dou` double NOT NULL,
  `num_flo` float NOT NULL,
  `test_test` text NOT NULL,
  `joinnum` bigint NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

###t2###

CREATE TABLE `t2` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int NOT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `test_json` json NOT NULL,
  `num_dec` decimal(10,0) NOT NULL,
  `num_dou` double NOT NULL,
  `num_flo` float NOT NULL,
  `test_test` text NOT NULL,
  `joinnum` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_joinnum` (`joinnum`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

質問SQL
# SQLa.joinnum      bigint,b.joinnum     varchar。        ,          (      8.0,    hash join)。
root@mysql8 16:13:  [test]> select count(*) from t1 a join t2 b on a.joinnum=b.joinnum;
+----------+
| count(*) |
+----------+
|   249996 |
+----------+
1 row in set (0.67 sec)
root@mysql8 16:16:  [test]> explain select count(*) from t1 a join t2 b on a.joinnum=b.joinnum;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+-------+----------+---------------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows  | filtered | Extra                                                   |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+-------+----------+---------------------------------------------------------+
|  1 | SIMPLE      | a     | NULL       | ALL   | NULL          | NULL        | NULL    | NULL | 99605 |   100.00 | NULL                                                    |
|  1 | SIMPLE      | b     | NULL       | index | idx_joinnum   | idx_joinnum | 1022    | NULL | 99484 |    10.00 | Using where; Using index; Using join buffer (hash join) |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+-------+----------+---------------------------------------------------------+
2 rows in set, 3 warnings (0.00 sec)
root@mysql8 17:20:  [test]> show warnings;
+---------+------+-------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                               |
+---------+------+-------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'idx_joinnum' due to type or collation conversion on field 'joinnum'   |
| Warning | 1739 | Cannot use range access on index 'idx_joinnum' due to type or collation conversion on field 'joinnum' |
+---------+------+-------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)


SQLの上書き
#  a.joinnum      char  ,     。
root@mysql8 16:16:  [test]> select count(*) from t1 a join t2 b on convert(a.joinnum,char)=b.joinnum;
+----------+
| count(*) |
+----------+
|   100000 |
+----------+
1 row in set (0.37 sec)

root@mysql8 16:17:  [test]> explain select count(*) from t1 a join t2 b on convert(a.joinnum,char)=b.joinnum;
+----+-------------+-------+------------+------+---------------+-------------+---------+------+-------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref  | rows  | filtered | Extra                    |
+----+-------------+-------+------------+------+---------------+-------------+---------+------+-------+----------+--------------------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL        | NULL    | NULL | 99605 |   100.00 | NULL                     |
|  1 | SIMPLE      | b     | NULL       | ref  | idx_joinnum   | idx_joinnum | 1022    | func |     1 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+------+---------------+-------------+---------+------+-------+----------+--------------------------+
2 rows in set, 1 warning (0.00 sec)


データ不一致インスタンスSQL
#            
root@mysql8 17:34:  [test]> select a.joinnum,b.joinnum from t1 a join t2 b on a.joinnum=b.joinnum limit 10;
+-------------------+-------------------+
| joinnum           | joinnum           |
+-------------------+-------------------+
| 10000000000000001 | 10000000000000001 |
| 10000000000000002 | 10000000000000002 |
| 10000000000000005 | 10000000000000003 |
| 10000000000000004 | 10000000000000003 |
| 10000000000000003 | 10000000000000003 |
| 10000000000000005 | 10000000000000004 |
| 10000000000000004 | 10000000000000004 |
| 10000000000000003 | 10000000000000004 |
| 10000000000000005 | 10000000000000005 |
| 10000000000000004 | 10000000000000005 |
+-------------------+-------------------+
10 rows in set (0.02 sec)


1.2理論的根拠
https://dev.mysql.com/doc/refman/5.7/en/type-conversion.html
                 :

If one or both arguments are NULL, the result of the comparison is NULL, except for the NULL-safe <=> equality comparison operator. For NULL <=> NULL, the result is true. No conversion is needed.
           NULL,       NULL,  NULL-safe <=>         。  NULL <=> NULL,   true。    。
If both arguments in a comparison operation are strings, they are compared as strings.
                 ,             。
If both arguments are integers, they are compared as integers.
          ,            。
Hexadecimal values are treated as binary strings if not compared to a number.
        ,               。
If one of the arguments is a TIMESTAMP or DATETIME column and the other argument is a constant, the constant is converted to a timestamp before the comparison is performed. This is done to be more ODBC-friendly. This is not done for the arguments to IN(). To be safe, always use complete datetime, date, or time strings when doing comparisons. For example, to achieve best results when using BETWEEN with date or time values, use CAST() to explicitly convert the values to the desired data type.
       a TIMESTAMP  DATETIMEcolumn,         ,        ,          。       ODBC   。     ,       IN()。      ,      ,            ,        。  ,   BETWEEN                   ,   CAST()              。
A single-row subquery from a table or tables is not considered a constant. For example, if a subquery returns an integer to be compared to a DATETIME value, the comparison is done as two integers. The integer is not converted to a temporal value. To compare the operands as DATETIME values, use CAST() to explicitly convert the subquery value to DATETIME.
                  。  ,         DATETIME         ,            。         。       DATETIME      ,    CAST()          DATETIME。
If one of the arguments is a decimal value, comparison depends on the other argument. The arguments are compared as decimal values if the other argument is a decimal or integer value, or as floating-point values if the other argument is a floating-point value.
           ,           。               ,              ;           ,             。
In all other cases, the arguments are compared as floating-point (real) numbers. For example, a comparison of string and numeric operands takes place as a comparison of floating-point numbers.
        ,        (  )    。  ,              ,          。
  • 暗黙タイプ変換の発生を回避し、暗黙変換のタイプは主にフィールドタイプの不一致、inパラメータが複数のタイプ、文字セットタイプ、または校正規則の不一致などを含む
  • である.
  • 暗黙型変換は、インデックスの使用ができない、クエリの結果が不正確であるなどの原因となる可能性があるため、
  • に注意する必要がある.
  • 数値タイプの推奨は、フィールド定義時にintまたはbigintとして定義され、テーブル関連時に関連フィールドは、タイプ、文字セット、校正規則が一致する
  • を維持する必要があります.
  • 文字列が浮動小数点数に変換されると、数値ビット数が少なくとも17ビットで問題が発生します.

  • 浮動小数点数を使用すると、正確な値として格納されるのではなく近似値であるため、混同されることがあります.SQL文で記述される浮動小数点値は、内部で表される値とは異なる場合があります.比較で浮動小数点値を正確な値と見なしてみると、問題が発生する可能性があります.プラットフォームまたは依存性を実現するための制約もあります.FLOATおよびDOUBLEのデータ型は、これらの問題を受けている.DECIMAL列の場合、MySQLが実行する精度は65個の10進数であり、最も一般的な不正確性の問題を解決できるはずです.