函数和运算符是一回事:拿值换值,区别只在写法。 「有没有某个函数」取决于装的是哪一版,先清点再动手。 把函数包在列上,那一列的索引就废了——这是函数最常见的性能坑。
10.1 是这一章的导言,没有具体函数要记。它只定一件事:函数也是表达式。 这一章七篇,一个节一篇。后面六篇都是具体函数,这一篇先把「怎么判断能不能用」讲清。
一、函数就是「进去几个值,出来一个值」
四行的共同点:括号里有参数,括号外拿到一个值。 这一点和运算符完全一样,区别只在写法——运算符用符号,函数用名字加括号。
| 写法 | 结果 |
|---|---|
abs(-3) |
3 |
upper('ab') |
AB |
length('中文') |
2 |
round(2.567, 2) |
2.57 |
所以函数能出现的地方,运算符也能出现:SELECT 列表、WHERE、ORDER BY。
反过来也一样,能写 abs(x) 的地方就能写 x * -1。
第三行那个 length('中文') 给 2,不是 6。
SQLite 的 length 数的是字符数,MySQL 的 LENGTH 数的是字节数。
同一行字在 MySQL 里是 6 个字节(UTF-8 每个汉字三字节)。
要数字符得用 CHAR_LENGTH,用错一个,中文标题会被砍掉三分之二。
二、先清点:沙箱里有几个函数
一条 SQL 就能清点,这比翻手册靠谱。
PRAGMA function_list 是 SQLite 3.37 之后才有的,正好这个沙箱够新。
| 项 | 数 |
|---|---|
| 注册的函数条目(含重载) | 192 |
| 不同的函数名 | 168 |
以 json_ 开头的 |
17 |
条目数比函数名多,是因为同名函数可以有不同参数个数,那叫重载。
round(x) 和 round(x, n) 就是两条,用的时候引擎按参数个数挑。
第三行那 17 个 json_ 函数最有意思:它们不是核心函数,是编译时带进来的扩展。
所以「这台机器上有没有某个函数」取决于装的是哪一版,不是查手册能查出来的。
三、同名不同义:一张对照单
跨库搬 SQL 时最危险的不是「没有这个函数」,而是「有,但意思不一样」。 没有会报错,意思不一样不会报错,只会算错。
| 函数 | 沙箱(SQLite) | MySQL |
|---|---|---|
length('中文') |
2(字符数) | 6(字节数) |
round(2.5) |
3.0 | 3 |
round(-2.5) |
-3.0 | -3 |
substr(s, 2, 3) |
第 2 个字符起取 3 个 | 同左 |
第一行是「数字节还是数字符」。
第三行和第四行两边一致,都是「四舍五入、遇五远离零」。
但 Python 内置的 round 是「遇五取偶」,round(2.5) 给它 2。
三个地方、三种行为,名字都叫 round。
所以「同一个名字」不保证「同一个行为」,跨库搬 SQL 时这几处必须重新验。
验的办法不是读文档,是跑一个边界值看结果——比如 2.5 和 -2.5。
四、没有的函数:自己补一个
reverse 在沙箱里报 no such function,补上只要三行:写一个函数,挂上去。
def reverse(s):
return None if s is None else s[::-1]
c.create_function(u"reverse", 1, reverse)
挂上之后,它在 SQL 里跟内建函数没有区别,能出现在任何位置。
这一点很重要:它意味着「缺函数」从来不是硬障碍,只是多写三行。
reverse('中文abc') 给 cba文中,汉字没被拆开。
因为 Python 的字符串是「字符序列」,不是字节序列。
换成按字节倒序,一个汉字的三字节会被拆散,输出就是一串乱码。
「字符」和「字节」不是一回事,这一条在讲字符集时会反复出现。
五、把函数包在列上,索引就废了
这一条是函数最常见的性能坑,也是这一章最该记住的一句。
五万行的表,WHERE v > 3 走索引,WHERE abs(v) > 3 全表扫。
| 写法 | 执行计划 |
|---|---|
WHERE v > 3 |
SEARCH ... (v>?) |
WHERE abs(v) > 3 |
SCAN t |
原因很简单:索引里存的是 v 的原值,不是 abs(v) 算出来的值。
abs(v) > 3 这个条件对索引来说完全是陌生的,只能逐行算。
解法有两个:一是建「函数索引」(MySQL 8.0 支持,SQLite 叫表达式索引);
二是把条件改写到列这一侧——比如 v > 3 OR v < -3,两段都能走索引。
第二种更好,因为它不增加写入代价。
函数索引要多维护一份数据,写多的表要掂量一下。
六、动手 Demo:函数是什么,以及沙箱里到底有几个
这一段做四件事:跑四个函数看形状、清点沙箱里的函数、注册一个缺的、 最后用五万行的表把「函数废索引」量出来。
import sqlite3
c = sqlite3.connect(u":memory:")
# ① 清点
fl = c.execute(u"PRAGMA function_list").fetchall()
print(len(fl), len(set(x[0] for x in fl)))
# ② 缺的函数自己补
def reverse(s):
return None if s is None else s[::-1]
c.create_function(u"reverse", 1, reverse)
print(c.execute(u"SELECT reverse('中文abc')").fetchone())
# ③ 函数包在列上,索引用不上
c.execute(u"CREATE TABLE t (id INTEGER PRIMARY KEY, v REAL)")
c.executemany(u"INSERT INTO t VALUES (?, ?)",
[(i, (i * 37 % 100000) / 100.0) for i in range(1, 50001)])
c.execute(u"CREATE INDEX i_v ON t (v)")
for q in [u"SELECT COUNT(*) FROM t WHERE v > 3",
u"SELECT COUNT(*) FROM t WHERE abs(v) > 3"]:
print(c.execute(u"EXPLAIN QUERY PLAN " + q).fetchone()[-1])
实跑输出:
--------------------------------------------------------------------
第四十四课:函数是什么,以及沙箱里到底有几个
--------------------------------------------------------------------
**教材 10.1 是这一章的导言:函数是什么、怎么调、MySQL 分了几类。**
**这一节没有具体函数要记,但它定了一件事:函数和运算符一样,都是「拿值换值」。**
**所以这一课不抄分类表,改成清点:沙箱里有多少函数、哪些和 MySQL 同名同义、哪些同名不同义。**
一、函数就是「进去几个值,出来一个值」
--------------------------------------------------------------------
写法 结果
------------------------------
`SELECT abs(-3)` 3
`SELECT upper('ab')` AB
`SELECT length('中文')` 2
`SELECT round(2.567, 2)` 2.57
**四行的共同点:括号里有参数,括号外拿到一个值。**
**这一点和运算符一样,区别只在写法——运算符用符号,函数用名字加括号。**
**所以函数能出现的地方,运算符也能出现:`SELECT` 列表、`WHERE`、`ORDER BY`。**
**反过来也一样,能写 `abs(x)` 的地方就能写 `x * -1`。**
**第三行那个 `length('中文')` 给 2,不是 6。**
**SQLite 的 `length` 数的是字符数,MySQL 的 `LENGTH` 数的是字节数。**
**同一行字在 MySQL 里是 6 个字节(UTF-8 每个汉字三字节),要数字符得用 `CHAR_LENGTH`。**
**这是「同名不同义」的典型,后面每一类函数都会撞到几个。**
二、清点:沙箱里有 192 个函数
--------------------------------------------------------------------
项 数
-----------------------------
注册的函数条目(含重载) 192
不同的函数名 168
以 `json_` 开头的 17
**一条 SQL 就能清点,这比翻手册靠谱。**
**`PRAGMA function_list` 是 SQLite 3.37 之后才有的,正好这个沙箱够新。**
**条目数比函数名多,是因为同名函数可以有不同参数个数,那叫重载。**
**`round(x)` 和 `round(x, n)` 就是两条,用的时候引擎按参数个数挑。**
**最有意思的是第三行:17 个 `json_` 函数。**
**它们不是「核心函数」,是编译时带进来的扩展——这说明「有没有某个函数」取决于装的是哪一版。**
三、没有的函数:注册一个替身
--------------------------------------------------------------------
**`reverse` 直接报 `no such function: reverse`。**
写法 结果
-----------------------------
`reverse('abc')` cba
`reverse('中文abc')` cba文中
**三行代码就补上了:写一个 Python 函数,用 `create_function` 挂上函数名和参数个数。**
**挂上之后,它在 SQL 里跟内建函数没有区别,能出现在任何位置。**
**注意第二行:`'中文abc'` 反过来是 `cba文中`,汉字没被拆开。**
**因为 Python 的字符串是「字符序列」,不是字节序列。**
**换成按字节倒序,一个汉字的三字节会被拆散,输出就是一串乱码。**
**这个差别在讲字符集时会反复出现,先记住「字符」和「字节」不是一回事。**
四、同名不同义:一张对照单
--------------------------------------------------------------------
函数 沙箱(SQLite) MySQL
----------------------------------------------------------------
`length('中文')` 2(字符数) 6(字节数)
`length` 的数字符版 就是 `length` 本身 `char_length`
`round(2.5)` 3.0 3
`round(-2.5)` -3.0 -3
`substr(s, 2, 3)` 从第 2 个字符起取 3 个 同左,索引从 1 开始
**第一行和第二行是同一个坑:数字节还是数字符。**
**做「标题最多多少个字」这种校验,用错一个函数,中文标题会被砍掉三分之二。**
**第三行和第四行两边其实一致,都是「四舍五入、遇五远离零」。**
**但 Python 内置的 `round` 是「遇五取偶」,`round(2.5)` 给它 2,和这两个引擎都不一样。**
**所以「同一个名字」不保证「同一个行为」,跨库搬 SQL 时这几处必须重新验。**
五、把函数包在列上,索引就废了
--------------------------------------------------------------------
写法 执行计划 耗时
----------------------------------------------------------------------
`WHERE v > 3` SEARCH t USING COVERING INDEX i_v (v>?) 1.61 毫秒
`WHERE abs(v) > 3` SCAN t 6.51 毫秒
**第一行是 `SEARCH`,用上了索引;第二行是 `SCAN`,五万行挨个看。**
**耗时差了 4.0 倍,而且行数越多差得越多。**
**原因很简单:索引里存的是 `v` 的原值,不是 `abs(v)` 算出来的值。**
**`abs(v) > 3` 这个条件对索引来说完全是陌生的,只能逐行算。**
**解法有两个:一是建「函数索引」(MySQL 8.0 支持,SQLite 叫表达式索引),**
**二是把条件改写到列这一侧——比如 `v > 3 OR v < -3`,两段都能走索引。**
清点那两个数是 192 和 168:条目里有重载,所以名比条目少。
这个数会随版本变,所以「我这台机器上有没有」只能现场查。
注册那三行是这一章最实用的技巧。
教材里点名的函数,沙箱缺了十几个,全靠这个办法补上。
最后两行执行计划一个是 SEARCH、一个是 SCAN,差别就在「函数包没包在列上」。
这个差别在五万行时是四倍多,行数越多差距越大。
30 秒实验
- 把
length('中文')换成length('abc'),看它给几——英文下字节和字符是同一个数。 - 用
PRAGMA function_list数一数json_开头的有几个。 - 把
WHERE abs(v) > 3改成WHERE v > 3 OR v < -3,看执行计划变回SEARCH。
常见误区
- 以为「同名函数行为一样」,
LENGTH和length差了一个字节口径。 - 在
WHERE里包函数,索引废了还不知道,只觉得「这表怎么这么慢」。 - 看到「找不到函数」就以为环境装坏了,其实补三行就能用。
自测三问(点开对答案)
1. 函数和运算符有什么共同点? —— 都是「拿值换值」,能出现的位置也一样:`SELECT` 列表、`WHERE`、`ORDER BY`。 区别只在写法,运算符用符号,函数用名字加括号。 2. `length('中文')` 在沙箱和 MySQL 里为什么不一样? —— 沙箱的 `length` 数字符(给 2),MySQL 的 `LENGTH` 数字节(UTF-8 每个汉字三字节,给 6)。 MySQL 要数字符得用 `CHAR_LENGTH`。 3. 为什么 `WHERE abs(v) > 3` 用不上索引? —— 因为索引里存的是 `v` 的原值,不是 `abs(v)` 的结果。条件对索引来说是陌生的,只能逐行算。 要解决就建函数索引,或者把条件改写到列这一侧。下一篇 10.2 进数学函数,重点是「取整有三种口径」,混用会算错钱。
- ☐ (本篇完,下一篇 10.2 数学函数)
相关笔记
- 使用正则表达式查询
- MySQL 数据类型
-
- 🐬 MySQL必知必会/00-索引|45. 🐬 MySQL必知必会
- 🛢️ MySQL从入门到精通 · 索引