# [FAQ] TiDB 升级到 6.0 因 collation 导致 SQL 执行计划不符合预期

## Key Words

collation, new_collations_enabled_on_first_bootstrap, 6.0

## Description

从 TiDB v6.0 开始， TiDB 默认启用了新的 collation 框架，该框架在计算两表 join 时，如果 collation 不一致，可能将无法将 A 表的条件传递给 B 表，导致 B 表即使对应字段即使有索引也只能走全表扫描。 该表现行为与 MySQL 5.7 一致。

## Content

在启用新的 collation 框架下，以下两表 t1 使用 utf8mb4_bin, t2 使用的 collation 为 utf8mb4_general_ci

```sql
CREATE TABLE `t1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `idx_a` varchar(256) NOT NULL DEFAULT '',
  `value` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`) /\*T![clustered_index] CLUSTERED \*/,
  UNIQUE KEY `idx_a` (`idx_a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |

CREATE TABLE `t2` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `idx_a` varchar(256) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `value` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`) /\*T![clustered_index] CLUSTERED \*/,
  UNIQUE KEY `idx_a` (`idx_a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
```

可以看到，t1,t2 没有任何区别，除了 collate 不一样，在这种情况下，尝试 join t1,t2，join 条件使用 t1.idx_a=t2.idx_a, 在 collate 一致时，我们能简单地推出两表都会走 index scan

而在 collate 不一致时，它的执行结果如下

```sql
explain select t1.value as value1,t2.id,t1.idx_a from t1 join t2 on(t1.idx_a=t2.idx_a) where t1.idx_a='aaa' and t2.value='aaa';
+-------------------------------+----------+-----------+------------------------------+----------------------------------------------------------+
| id                            | estRows  | task      | access object                | operator info                                            |
+-------------------------------+----------+-----------+------------------------------+----------------------------------------------------------+
| Projection_8                  | 1.25     | root      |                              | ticket.t1.value, ticket.t2.id, ticket.t1.idx_a           |
| └─HashJoin_24                 | 1.25     | root      |                              | inner join, equal:\[eq(ticket.t1.idx_a, ticket.t2.idx_a)] |
|   ├─Point_Get_28(Build)       | 1.00     | root      | table:t1, index:idx_a(idx_a) |                                                          |
|   └─TableReader_27(Probe)     | 10.00    | root      |                              | data:Selection_26                                        |
|     └─Selection_26            | 10.00    | cop\[tikv] |                              | eq(ticket.t2.value, "aaa")                               |
|       └─TableFullScan_25      | 10000.00 | cop\[tikv] | table:t2                     | keep order:false, stats:pseudo                           |
+-------------------------------+----------+-----------+------------------------------+----------------------------------------------------------+

```

也就是说，表 t2 并没有走 indexscan, 而是走了 tablescan, 这是为什么呢？因为当 collate 不一致时，是无法通过 t1.idx_a 直接传递给 t2.idx_a, 所以这边最终还是使用了全表扫。

下面，我们创建一张与 t1  collate 一样的 t3, 再执行以上语句时，就可以看到 t2 也走了唯一索引了

```sql
CREATE TABLE `t3` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `idx_a` varchar(256) NOT NULL DEFAULT '',
  `value` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`) /\*T![clustered_index] CLUSTERED \*/,
  UNIQUE KEY `idx_a` (`idx_a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
explain select t1.value as value1,t3.id,t1.idx_a from t1 join t3 on(t1.idx_a=t3.idx_a) where t3.idx_a='aaa' and t3.value='aaa';
+-------------------------------+---------+------+------------------------------+-----------------------------------------------------------------+
| id                            | estRows | task | access object                | operator info                                                   |
+-------------------------------+---------+------+------------------------------+-----------------------------------------------------------------+
| Projection_9                  | 0.00    | root |                              | ticket.t1.value, ticket.t3.id, ticket.t1.idx_a                  |
| └─Projection_10               | 0.00    | root |                              | ticket.t1.idx_a, ticket.t1.value, ticket.t3.id                  |
|   └─MergeJoin_11              | 0.00    | root |                              | inner join, left key:ticket.t3.idx_a, right key:ticket.t1.idx_a |
|     ├─Point_Get_45(Build)     | 1.00    | root | table:t1, index:idx_a(idx_a) |                                                                 |
|     └─Selection_44(Probe)     | 0.00    | root |                              | eq(ticket.t3.value, "aaa")                                      |
|       └─Point_Get_43          | 1.00    | root | table:t3, index:idx_a(idx_a) |                                                                 |
+-------------------------------+---------+------+------------------------------+-----------------------------------------------------------------+

```

综上，collate 不一致时，确实会导致部分条件无法在表之间传递，这与 MySQL 5.7 的行为是一致的。

特别注意的是，TiDB 只有在采用了新的 collation 框架时，才会是以上预期行为。在未启用新的 collation 框架时，执行计划的行为不可预期。

建议用户在集群启动时使用新的 collation 框架，相关参数为 [new_collations_enabled_on_first_bootstrap](https://docs.pingcap.com/zh/tidb/v6.0/tidb-configuration-file#new_collations_enabled_on_first_bootstrap)，v6.0.0 开始默认启用。

## Affected Versions

新 collate 框架的使用：

* v6.X 默认启用
* v4.0.0-v5.X 需要在集群初始化时开启 new_collations_enable_on_first_bootstrap 才会有效，否则使用旧框架。