Skip to content

23 10+ SQL执行性能不佳的真实案例(上)

你好,我是俊达。

在第二章 SQL优化篇的前面几讲中,我们比较系统地学习了MySQL中SQL优化的基础知识,包括索引访问的原理、优化器成本模型、表连接的几种算法、表连接顺序的计算、子查询的优化策略。在这一讲里,我整理了工作中遇到过的十多种不同类型的SQL性能问题,以案例的形式来讲解SQL优化的一些思路。

案例一:索引缺失引起的全表扫描

这是一个非常简单的SQL。

SELECT *
FROM template
WHERE templet_id = 2 AND status = 1

我们来看一下这个SQL的执行计划。

图片

从这个执行计划里,我们要关注这些信息:

  • table:访问的表,这里是template表。

  • type:ALL,表示全表扫描。

  • possible_keys:这个查询没有索引可以使用。

  • key:查询没有使用索引。

  • key_len:使用到的索引长度。

  • rows:预估需要访问的数据,这里是3.6万多。

从执行计划可以看到,这个查询需要扫描3.6万行数据,没有任何索引可以使用。对于这种场景,我们可以给过滤性高的条件建立索引。

alter table template
    add key idx_templateid_status(templet_id, status);

这里的关键是过滤性,如果过滤性不高,那么建了索引也不一定能提升性能。

过滤性是什么意思呢?可以理解为使用给定的where条件过滤出来的记录数占总记录数的比例,这个比例越小,那么使用索引的效果越好。我们可以用下面这个SQL来分析一个字段或多个字段的过滤性。

select 1 / count(distinct c) from tab;

字段的唯一值越多,那么上面这个SQL的查询值就越小,过滤性越高。如果过滤性为1,就说明这个字段的数据都是相同的。

在有些业务场景下,可能会存在数据倾斜,某些值的数据特别多。可以用下面这个SQL来分析是否存在数据倾斜。

select col, count(*)
from tab
group by col
order by count(*)
desc limit 10

如果有数据倾斜,那么索引是否有效,取决于where条件中的传入的值的过滤性,过滤性高的条件组合可以用索引来提升查询效率。在这个案例中,我们建立了一个组合索引:

idx_templateid_status(templet_id, status)

组合索引中字段的顺序很重要,要结合业务系统中的其他SQL来综合考虑。当然仅仅就我们案例中的这个SQL来说,字段顺序取(templet_id, status)或(status, templet_id)都可以。

案例二:优化排序操作

应用程序经常需要对访问的数据进行排序。下面这个SQL中,使用了order by。

SELECT *
FROM audit_log
WHERE user_id = ?
AND log_type = ?
ORDER BY gmt_create DESC

我们来看一下执行计划。

图片

  • key: idx_userid_logtype,使用了idx_userid_logtype索引。

  • key_len: 8,user_id为bigint

  • ref: const。

  • Extra: Using where; Using filesort。

从执行计划中可以看到,SQL使用了文件排序(filesort)。排序会消耗CPU和内存资源。如果待排序的数据超过了sort_buffer_size,需要将数据写入临时文件,进行真正的文件排序。

类似上面的这个SQL,我们可以利用索引的有序性来避免排序。在索引中加上排序字段gmt_create。

alter table audit_log
   drop key idx_userid_logtype,
   add KEY `idx_userid_logtype` (
     `user_id`,`log_type`,`gmt_create`);

查看执行计划,可以看到Extra中没有了filesort了。

图片

SQL需要满足几个条件,才能使用索引来消除排序。

  1. 排序字段前面的字段(这个例子中是user_id, log_type),需要以等值条件传入到where条件中(user_id=xx and log_type=xx)。

  2. 如果按多个字段排序 (order by col_1, col_2),那么索引中这几个字段也要以同样的顺序建立,idx(col_1, col_2)。

对于例子中的这个索引 idx(user_id, log_type, gmt_create),如果SQL缺少log_type的条件,就无法使用索引来消除排序了。

SELECT *
FROM audit_log
WHERE user_id = ?
ORDER BY gmt_create DESC

案例三:函数运算导致无法使用索引

有的时候,明明字段上已经建立了索引,但是查询还是无法使用索引。其中有一种情况是因为SQL中对索引字段进行了函数运算。下面就是一个比较常见的例子,SQL在gmt_modified字段上使用了date函数,用来查询当天修改过的数据。

select * from user
where date(gmt_modified) = date(now())

我们来看一下执行计划。

图片

可以看到,possible_keys是空的,没有任何索引可用。实际上gmt_modified上是有索引的。但是由于where条件中对gmt_modified做了函数运算,导致无法使用索引。我们需要改写SQL,避免在索引字段上使用函数,同时还要保持SQL的逻辑不变。

这个案例中,SQL可以这么改。

select * from user
where gmt_modified >= date(now())
and gmt_modified < date(date_add(now(), interval 1 day))

这2个SQL的逻辑一样,都是获取修改时间为当天的用户信息。

我们来看一下改写后的执行计划。

图片

可以看到,SQL使用了idx_gmt_modified索引,扫描的记录数为3119。而之前全表扫描需要访问2.8万行数据。

MySQL 8.0支持函数索引,上面这个SQL,也能用函数索引来优化。我们来做一个简单的验证,先创建一个表,写入一些测试数据,并创建一个函数索引。

mysql> create table t_date(
  id int not null auto_increment,
  create_time datetime,
  padding varchar(2000),
  primary key(id)
) engine=innodb;

mysql> insert into t_date(create_time, padding)
  select date_add('2024-06-01 00:00:00', interval n hour),
      rpad('x', 1000, 'abcd ')
  from numbers;

mysql> alter table t_date
    add key idx_createtime((date(create_time)));

下面这个SQL中,对索引字段进行了函数运算。从执行计划里可以看到,SQL用到了索引。

mysql> explain select * from t_date
    where date(create_time) = '2024-06-01';
+----+-------------+--------+------+----------------+----------------+---------+-------+------+----------+-------+
| id | select_type | table  | type | possible_keys  | key            | key_len | ref   | rows | filtered | Extra |
+----+-------------+--------+------+----------------+----------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t_date | ref  | idx_createtime | idx_createtime | 4       | const |   24 |   100.00 | NULL  |
+----+-------------+--------+------+----------------+----------------+---------+-------+------+----------+-------+

mysql> show warnings\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */
  select `rep`.`t_date`.`id` AS `id`,
    `rep`.`t_date`.`create_time` AS `create_time`,
    `rep`.`t_date`.`padding` AS `padding`
  from `rep`.`t_date`
  where (cast(`create_time` as date) = '2024-06-01')

索引字段上的函数运算还会以其他形式出现,比如下面这两个SQL。

select * from t1 where b+0 = ?;
select * from t1 where c||'' = ?;

案例四:隐式类型转换

从上一个案例中我们已经知道,字段上的函数运算会导致SQL无法使用索引。有些情况下,虽然没有显式地对字段进行运算,但由于字段和传入的参数的数据类型不一致,数据库会进行隐式类型转换,这也会导致索引不可用。

下面是隐式类型转换常见的几种情况。

  1. 字段类型为varchar,存储了数字,where条件中传入了数字类型的参数。

  2. 字段类型为varchar,存储了日期信息,where条件中传入了日期类型的参数。

  3. 字段类型和传入的参数都是varchar类型,但是字符集不匹配。

  4. 表连接时,连接字段在2个表中的类型不一致,或字符集不一致。

我们用一个例子来验证哪些情况下,隐式类型转换会导致索引不可用。

CREATE TABLE `tab` (
  `id` int NOT NULL AUTO_INCREMENT,
  `phone` varchar(13) DEFAULT NULL,
  `phone2` bigint DEFAULT NULL,
  `create_time` varchar(20) DEFAULT NULL,
  `create_time2` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_phone` (`phone`),
  KEY `idx_phone2` (`phone2`),
  KEY `idx_createtime` (`create_time`),
  KEY `idx_createtime2` (`create_time2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

情况1:索引字段为varchar,传入数字类型的参数,索引不可用。从warning信息中可以看到,索引不可用的原因是发生了类型转换。

mysql> explain  select * from tab where phone=13512345678;
+----+-------------+-------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tab   | ALL  | idx_phone     | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)

mysql> show warnings;
+---------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                                                                                                                                                                  |
+---------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'idx_phone' due to type or collation conversion on field 'phone'                                                                                                                                                                          |
| Warning | 1739 | Cannot use range access on index 'idx_phone' due to type or collation conversion on field 'phone'                                                                                                                                                                        |
| Note    | 1003 | /* select#1 */ select `test`.`tab`.`id` AS `id`,`test`.`tab`.`phone` AS `phone`,`test`.`tab`.`phone2` AS `phone2`,`test`.`tab`.`create_time` AS `create_time`,`test`.`tab`.`create_time2` AS `create_time2` from `test`.`tab` where (`test`.`tab`.`phone` = 13512345678) |
+---------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

将传入的参数改成字符串类型,就可以使用到索引。

mysql> explain  select * from tab where phone='13512345678';
+----+-------------+-------+------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table | type | possible_keys | key       | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------+---------------+-----------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | tab   | ref  | idx_phone     | idx_phone | 55      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------+---------------+-----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

情况2:索引字段类型为varchar,传入日期类型的参数,索引不可用。从warning信息中可以看到,索引不可用的原因是发生了类型转换。

mysql> explain  select * from tab where create_time = date(now());
+----+-------------+-------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table | type | possible_keys  | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------+----------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tab   | ALL  | idx_createtime | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-------+------+----------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)

mysql> show warnings;
+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                                                                                                                                                                                         |
+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'idx_createtime' due to type or collation conversion on field 'create_time'                                                                                                                                                                                      |
| Warning | 1739 | Cannot use range access on index 'idx_createtime' due to type or collation conversion on field 'create_time'                                                                                                                                                                                    |
| Note    | 1003 | /* select#1 */ select `test`.`tab`.`id` AS `id`,`test`.`tab`.`phone` AS `phone`,`test`.`tab`.`phone2` AS `phone2`,`test`.`tab`.`create_time` AS `create_time`,`test`.`tab`.`create_time2` AS `create_time2` from `test`.`tab` where (`test`.`tab`.`create_time` = <cache>(cast(now() as date))) |
+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

情况3:字段类型为数字,传入字符串类型的参数,是否也会导致索引不可用呢?

我们来测试一下。

mysql> explain  select * from tab where phone2 = '13512345678';
+----+-------------+-------+------+---------------+------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-------+------+---------------+------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | tab   | ref  | idx_phone2    | idx_phone2 | 9       | const |    1 |   100.00 | Using index condition |
+----+-------------+-------+------+---------------+------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> show warnings\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */
select `test`.`tab`.`id` AS `id`,`test`.`tab`.`phone` AS `phone`,
    `test`.`tab`.`phone2` AS `phone2`,`test`.`tab`.`create_time` AS `create_time`,
    `test`.`tab`.`create_time2` AS `create_time2`
from `test`.`tab`
where (`test`.`tab`.`phone2` = 13512345678)

上面的例子中,phone2为数字类型,SQL中传入了字符串类型的参数,索引可用,因为这里类型转换发生在传入的参数上。类似的,下面这种情况也不影响索引的使用。

mysql> explain  select * from tab where create_time2 = '2023-01-01';
+----+-------------+-------+------------+------+-----------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys   | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | tab   | NULL       | ref  | idx_createtime2 | 6       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+-----------------+---------+-------+------+----------+-------+1 row in set, 1 warning (0.00 sec)

mysql> show warnings\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */
select `test`.`tab`.`id` AS `id`,`test`.`tab`.`phone` AS `phone`,
  `test`.`tab`.`phone2` AS `phone2`,`test`.`tab`.`create_time` AS `create_time`,
  `test`.`tab`.`create_time2` AS `create_time2`
from `test`.`tab`
where (`test`.`tab`.`create_time2` = TIMESTAMP'2023-01-01 00:00:00')

为了避免隐式类型转换导致索引不可用,我有以下几点建议。

  1. 设计表结构时,根据业务需求选择精确的字段类型,不要用varchar类型存储日期和数字。

  2. 同一个业务字段,在不同的表里面字段类型要保持一致,字符集也要保持一致。

  3. SQL中传入的参数要和字段的数据类型保持一致。

案例五:字符集不一致导致的隐式类型转换

执行表连接时,如果连接字段在两个表中的字符集不一致,也会发生隐式类型转换,也可能会导致索引不可用。下面这个例子就是这种情况。

SELECT *
FROM funds
WHERE  uuid  in (
    SELECT uuid
    FROM patients
    WHERE create_at != "0000-00-00 00:00:00"
)

先看一下执行计划。

图片

优化器将子查询转换成了半连接,并使用了临时表来去重(Start temporary,End temporary)。被驱动表funds的uuid上有唯一索引,但是执行计划中显示这个表没有可用的索引。查询使用BNL连接算法(Block Nested Loop)。

执行explain extended后查看warnings(注:8.0已经不支持explain extended了),发现SQL中有字符集转换的操作convert(b.uuid using utf8mb4)。

mysql> explain extended
    select b.*
    from patients a, funds b
    where a.create_at != "0000-00-00 00:00:00"
    and a.uuid=b.uuid

mysql> show warnings
select *
from patients a
     join funds b
where((a.create_at <> '0000-00-00 00:00:00')
and(a.uuid= convert(b.uuid using utf8mb4)))

检查表结构后发现,两个表中uuid的字符集不一样。

CREATE TABLE `funds` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `uuid` varchar(128) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'UUID',
     ......,
 PRIMARY KEY (`id`),
UNIQUE KEY `uuid_idx` (`uuid`))
ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `patients` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `uuid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '项目uuid',
PRIMARY KEY (`id`))
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

funds表中uuid的字符集是utf8,patients表中uuid的字符集是utf8mb4。

我们改写了SQL,使用convert函数将patients.uuid转换成utf8,载去和funds表连接,这样就避免了funds.uuid上的隐式类型转换。

explain extended
 SELECT b.*
FROM (select convert(uuid using utf8) COLLATE utf8_unicode_ci as uuid
    from patients
    where project_create_at != "0000-00-00 00:00:00") a, funds b
WHERE a.uuid = b.uuid

我们来看一下SQL改写后的执行计划,可以用到funds表中uuid的唯一索引了,type为eq_ref。

图片

为了避免表连接时发生隐式类型转换,我建议同一个业务字段,在不同的表里,数据类型和字符集要保持一致。

优化器在转换字符集时,选择了将utf8mb3转换成utf8mb4。下面是一个测试案例,可以说明优化器的这个选择。

先创建两个测试表。

create table t_1(
  id int not null auto_increment,
    uuid varchar(32),
    padding varchar(2000),
    primary key(id),
    key idx_uuid(uuid)
) engine=innodb charset utf8mb3;

create table t_2(
  id int not null auto_increment,
    uuid varchar(32),
    padding varchar(2000),
    primary key(id),
    key idx_uuid(uuid)
) engine=innodb charset utf8mb4;

t_1表使用utf8mb3字符集,t_2表使用utf8mb4字符集。以t_1表作为驱动表时,可以使用t_2表uuid的索引,因为t_2.uuid没有发生类型转换。

mysql> explain select * from t_1 straight_join t_2 on t_1.uuid = t_2.uuid;
+----+-------------+-------+------+---------------+-----------+---------+--------------+------+----------+-----------------------+
| id | select_type | table | type | possible_keys | key       | key_len | ref          | rows | filtered | Extra                 |
+----+-------------+-------+------+---------------+-----------+---------+--------------+------+----------+-----------------------+
|  1 | SIMPLE      | t_1   | ALL  | idx_uuid1     | NULL      | NULL    | NULL         |    1 |   100.00 | Using where           |
|  1 | SIMPLE      | t_2   | ref  | idx_uuid2     | idx_uuid2 | 131     | rep.t_1.uuid |    1 |   100.00 | Using index condition |
+----+-------------+-------+------+---------------+-----------+---------+--------------+------+----------+-----------------------+
2 rows in set, 2 warnings (0.00 sec)

mysql> show warnings\G
*************************** 1. row ***************************
  Level: Warning
   Code: 1739
Message: Cannot use ref access on index 'idx_uuid1' due to type or
 collation conversion on field 'uuid'
*************************** 2. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */
select `rep`.`t_1`.`id` AS `id`,`rep`.`t_1`.`uuid` AS `uuid`,
  `rep`.`t_1`.`padding` AS `padding`,`rep`.`t_2`.`id` AS `id`,
  `rep`.`t_2`.`uuid` AS `uuid`,`rep`.`t_2`.`padding` AS `padding`
  from `rep`.`t_1` straight_join `rep`.`t_2`
  where (`rep`.`t_1`.`uuid` = `rep`.`t_2`.`uuid`)

以t_2表作为驱动表时,无法使用t_1表uuid的索引,因为t_1.uuid发生了类型转换。此时优化器使用了Hash连接。

mysql> explain select * from t_2 straight_join t_1 on t_1.uuid = t_2.uuid;
+----+-------------+-------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t_2   | ALL  | idx_uuid2     | NULL | NULL    | NULL |    1 |   100.00 | NULL                                       |
|  1 | SIMPLE      | t_1   | ALL  | idx_uuid1     | NULL | NULL    | NULL |    1 |   100.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
2 rows in set, 3 warnings (0.00 sec)

mysql> show warnings\G
*************************** 1. row ***************************
  Level: Warning
   Code: 1739
Message: Cannot use ref access on index 'idx_uuid1' due to type or
  collation conversion on field 'uuid'
*************************** 2. row ***************************
  Level: Warning
   Code: 1739
Message: Cannot use range access on index 'idx_uuid1' due to type or
  collation conversion on field 'uuid'
*************************** 3. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */
select `rep`.`t_2`.`id` AS `id`,`rep`.`t_2`.`uuid` AS `uuid`,
  `rep`.`t_2`.`padding` AS `padding`,`rep`.`t_1`.`id` AS `id`,
  `rep`.`t_1`.`uuid` AS `uuid`,`rep`.`t_1`.`padding` AS `padding`
from `rep`.`t_2` straight_join `rep`.`t_1`
where (`rep`.`t_1`.`uuid` = `rep`.`t_2`.`uuid`)

案例六:优化or条件

很多情况下,优化器可以自动选择一个最优的执行计划。但由于SQL的写法很多,在一些场景下,优化器选择的执行计划效率并不高。这时就可能需要改写SQL,帮助优化器找到更好的执行计划。where子句中不同的字段上的条件使用or相连,就比较容易引起性能问题。

下面这个SQL来自一个真实的业务系统。

SELECT count(1)
FROM car
WHERE is_deleted = 0
AND (seller_id = 100
     OR (creator = 200 AND seller_id = -1))
AND car_id NOT IN (
    SELECT car_id
    FROM product
    WHERE product_type = 2
    AND source = 2)

先看一下执行计划,主查询中使用索引访问16万行数据。子查询这里只需要执行一次。

图片

上面的SQL中,seller_id=100和creator = 200这两个条件的过滤性都不错,而且也都建了索引,但是这2个条件使用了OR。优化器最终选择了seller_id上的索引,但是seller_id = -1这个条件的过滤性并不好。

MySQL支持index_merge的执行计划,理论上可以用seller_id=100和creator=200过滤数据,再把数据合并起来,但是这里优化器并没有采用这个执行计划。因此我们将SQL做了改写,将SQL拆分成2个部分,使用union all合并数据。

 SELECT sum(a) FROM (
    SELECT count(1) as a FROM car WHERE is_deleted = 0 AND car_id NOT IN ( SELECT car_id  FROM product WHERE product_type = 2 AND source = 2)
AND (seller_id = 100)
    union all
    SELECT count(1) as a FROM car WHERE is_deleted = 0 AND car_id NOT IN (SELECT car_id  FROM product WHERE product_type = 2 AND source = 2)
AND creator = 1000  AND seller_id = -1
) t

看一下改写后的执行计划。

图片

改写之后,union all的2个SQL片段使用了不同的索引,扫描的行数分别是1238和7130,比改写之前提升了一个数量级。这样改写之后,执行计划也会更稳定。

总结

这一讲的六个案例,在平时工作中比较常见,通常也比较容易解决。首先要理解索引的选择性,为查询中过滤性高的条件创建合适的索引。对于隐式类型转换,最重要的是要在建表的时候就选择精确的数据类型,避免使用varchar来存储数字类的、日期时间类的数据。字符集也要保持一致。如果表已经建好,系统已经上线运行了,那就要在应用代码里使用和字段类型匹配的数据类型。

思考题

有时候我们会遇到执行计划选择了错误的索引,导致SQL性能比较差。一个可能的解决方案是使用force index强制索引。使用force index可能会存在哪些潜在的风险?有没有其他办法来避免执行计划选错索引?

期待你的思考,欢迎在留言区中与我交流。如果今天的课程让你有所收获,也欢迎转发给有需要的朋友。我们下节课再见!