MySQL 函数索引功能终于可以实现了

287dfa765984043a6e35267e7af8e5cc.png

开头还是介绍一下群,如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,SQL Server,Redis ,Oracle ,Oceanbase 等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。加群请加微信号 liuaustin3 (共1280人左右 1 + 2 + 3 +4)新人会进入3群(3群接近400准备关闭自由申请) + 4群

当对你热情的人,突然不热情了,说明你的利用的价值已经不存在了,此时没有必要刨根问底,为什么他会对你这样,人性如此,只是你懂得的太晚了而已。

升级MySQL 到MySQL8 是很多企业都还没有做的事情,可能是诱惑力不够,在SQL 的进步方面MySQL 的确是说一说,今天来说说函数索引的问题。

在不少的SQL语句中撰写中,MySQL都会强调不允许存在条件左边有函数的情况,但这对于其他的数据库来说并不是一个必须的要求,因为其他的数据库大多支持函数索引的问题,这就导致MySQL 在语句查询和索引方的太简单的问题,比较显露。

在另一个开源数据库PostgreSQL的guide中写明了,使用函数索引的情况下,索引的表达式在索引的搜索期间不会重新计算,以为他们已经存储在索引中,查询中将查询视为  where 索引列=‘常量’ ,搜索的速度与普通的简单查询是类似的。

MySQL 在8.013的版本中开始支持函数索引,函数索引允许基于表中某一个列的计算或函数来进行索引的建立。

mysql> 
mysql> explain analyze select * from orders where month(orderDate) = '01';
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                       |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: (month(orders.orderDate) = '01')  (cost=33.35 rows=326) (actual time=0.031..0.254 rows=25 loops=1)
    -> Table scan on orders  (cost=33.35 rows=326) (actual time=0.027..0.229 rows=326 loops=1)
 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>

上面的部分,在之前MySQL是无法接受这样的写法的,必须转换写法才能完成这样的查询功能并且有效率的工作。

下面我们用两种方法来进行相关问题的解决,基础表

CREATE TABLE `orders` (
  `orderNumber` int NOT NULL,
  `orderDate` date NOT NULL,
  `requiredDate` date NOT NULL,
  `shippedDate` date DEFAULT NULL,
  `status` varchar(15) NOT NULL,
  `comments` text,
  `customerNumber` int NOT NULL,
  PRIMARY KEY (`orderNumber`),
  KEY `customerNumber` (`customerNumber`),
  KEY `idx_orderdate` ((dayofmonth(`orderDate`))),
  CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`customerNumber`) REFERENCES `customers` (`customerNumber`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |

1  函数索引

create index idx_orderdate on orders ((day(orderDate)));
mysql> explain analyze select count(*) from orders where day(orderDate) = '03';
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                          |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Aggregate: count(0)  (cost=65.95 rows=1) (actual time=0.126..0.126 rows=1 loops=1)
    -> Filter: (dayofmonth(orders.orderDate) = '03')  (cost=33.35 rows=326) (actual time=0.043..0.123 rows=16 loops=1)
        -> Table scan on orders  (cost=33.35 rows=326) (actual time=0.036..0.097 rows=326 loops=1)
 |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

在查询中,发现并没有走索引的原因,虽然添加了函数索引,那么我们变换一下相关的写法

mysql> explain analyze select count(*) from orders where day(orderDate) = day('2023-09-01');
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                  |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Aggregate: count(0)  (cost=5.25 rows=1) (actual time=0.057..0.057 rows=1 loops=1)
    -> Index lookup on orders using idx_orderdate (dayofmonth(orderDate)=dayofmonth('2023-09-01'))  (cost=3.75 rows=15) (actual time=0.049..0.054 rows=15 loops=1)
 |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>

在我们进行了相关的查询的方式改写后,发现可以走索引了,但是原因是什么,原因在于隐式转换,之前在 day 函数操作后的数据并不是文本,所以需要将等于号后面的文字,标记为数值,或不添加引号。

mysql> explain analyze select count(*) from orders where day(orderDate) = 01;
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                           |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Aggregate: count(0)  (cost=5.25 rows=1) (actual time=0.147..0.147 rows=1 loops=1)
    -> Index lookup on orders using idx_orderdate (dayofmonth(orderDate)=1)  (cost=3.75 rows=15) (actual time=0.130..0.140 rows=15 loops=1)
 |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


或者改写成下方的方式,都可以避免隐士转换导致的索引问题。

mysql> explain analyze select count(*) from orders where day(orderDate) = cast("7" as unsigned) ;
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                             |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Aggregate: count(0)  (cost=4.05 rows=1) (actual time=0.048..0.049 rows=1 loops=1)
    -> Index lookup on orders using idx_orderdate (dayofmonth(orderDate)=cast('7' as unsigned))  (cost=3.15 rows=9) (actual time=0.042..0.045 rows=9 loops=1)
 |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>

这是目前 8.013 版本以上的MySQL 可以提供的方案。

第二种方案是通过,添加虚拟列的方式来进行相关的工作,我们先添加一个虚拟列。

| orders | CREATE TABLE `orders` (
  `orderNumber` int NOT NULL,
  `orderDate` date NOT NULL,
  `requiredDate` date NOT NULL,
  `shippedDate` date DEFAULT NULL,
  `status` varchar(15) NOT NULL,
  `comments` text,
  `customerNumber` int NOT NULL,
  PRIMARY KEY (`orderNumber`),
  KEY `customerNumber` (`customerNumber`),
  KEY `idx_orderdate` ((dayofmonth(`orderDate`))),
  CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`customerNumber`) REFERENCES `customers` (`customerNumber`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> alter table orders add column day_t smallint generated always as (day(orderdate)) virtual;
Query OK, 326 rows affected (0.09 sec)
Records: 326  Duplicates: 0  Warnings: 0

在添加完虚拟列后,我们对虚拟列进行索引的添加,再次查询,我们可以看到在实际的操作中我们已经可以走索引了。以上就是 2中在MySQL8中对于一些特殊的查询需求中走索引的方案。

mysql> create index idx_day_t on orders (day_t);
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from orders where day_t = 7;
+----+-------------+--------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table  | partitions | type | possible_keys | key       | key_len | ref   | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | orders | NULL       | ref  | idx_day_t     | idx_day_t | 3       | const |    9 |   100.00 | NULL  |
+----+-------------+--------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> explain analyze select * from orders where day_t = day('2022-09-09');
+---------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                     |
+---------------------------------------------------------------------------------------------------------------------------------------------+
| -> Index lookup on orders using idx_day_t (day_t=dayofmonth('2022-09-09'))  (cost=3.55 rows=13) (actual time=0.051..0.111 rows=13 loops=1)
 |
+---------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
### 函数索引反转的概念 在数据库查询优化中,函数索引反转是指通过调整查询条件或创建特定类型的索引来提高涉及函数操作字段的查询性能。当查询涉及到对列应用函数时,默认情况下无法利用该列上的常规索引,因为索引是基于原始数据构建的而不是基于经过函数处理后的结果。 对于 `reverse` 这样的字符串反转函数,在某些场景下可能需要频繁地根据反向排列的数据进行匹配查找。如果直接使用 `LIKE` 或者其他模式匹配方式来搜索被反转过的字符串,则效率较低;此时可以通过预先计算并存储这些值或者建立适合此类操作使用的特殊结构——即所谓的“函数索引”。 然而需要注意的是,并不是所有的 MySQL 版本都支持真正的函数索引特性。直到版本 8.0 开始引入了表达式索引功能,允许为计算得出的结果集创建索引,这其中包括但不限于调用了内置函数的情况[^2]。 ### 实现方法 假设有一个表名为 `users` 的表格,其中有一列表示用户的手机号码叫做 `mobile` ,现在想要快速定位那些号码结尾部分为 "1234" 的记录: #### 方法一:使用虚拟生成列配合普通索引 ```sql ALTER TABLE users ADD COLUMN reversed_mobile VARCHAR(255) GENERATED ALWAYS AS (REVERSE(mobile)) VIRTUAL; CREATE INDEX idx_reversed_mobile ON users(reversed_mobile); SELECT * FROM users WHERE REVERSE(mobile) LIKE '4321%'; -- 或更高效的方式 SELECT * FROM users WHERE reversed_mobile LIKE '4321%'; ``` 这种方法先增加了一个新的虚拟(VIRTUAL)自动生成列 `reversed_mobile` 来保存原电话号码对应的逆序形式,接着针对这个新添加的属性建立了标准 B-tree 类型的索引 `idx_reversed_mobile` 。最后执行查询的时候就可以直接比较已经预处理好的内容而不需要每次都重新计算一次 `REVERSE()` 函数返回值了。 #### 方法二:直接定义表达式索引(仅限于 MySQL 8.0 及以上) ```sql CREATE INDEX idx_expr_reversed_mobile ON users ((REVERSE(mobile))); EXPLAIN SELECT * FROM users WHERE REVERSE(mobile) LIKE '4321%'; ``` 这里直接指定了要作为键值的部分是一个 SQL 表达式的输出而非简单的单个物理存在字段名 `(REVERSE(mobile))` 。这种方式更加简洁明了而且无需额外占用磁盘空间用于维护辅助性的中间状态信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值