@@ -1108,7 +1108,162 @@ Percona Toolkit 中的 `pt-query-advisor` 能够解析查询日志、分析查
1108
1108
1109
1109
==== 使用用户自定义变量
1110
1110
1111
- _待补充_
1111
+ 用户自定义变量是一个用来存储内容的临时容器,在连接 MySQL 的整个过程中都存在。
1112
+
1113
+ 不能使用用户自定义变量的场景:
1114
+
1115
+ * 使用自定义变量的查询,无法使用查询缓存。
1116
+ * 不能再使用常量或者标识符的地方使用自定义变量,例如表名等。
1117
+ * 用户自定义变量的生命周期是在一个连接中有效,所以不能用它们来做连接间的通信。
1118
+ * 如果使用连接池或者持久化连接,自定义变量可能让看起来毫无关系的代码发生交互。
1119
+ * 在 5.0 之前的版本,是大小写敏感的。
1120
+ * 不能显式地声明自定义变量的类型。
1121
+ * MySQL 优化器在某些场景下可能会将这些变量优化掉。
1122
+ * 赋值的顺序和赋值的时间点并不总是固定的,这依赖于优化器的决定。
1123
+ * 赋值符号 `:=` 的优先级非常低。
1124
+ * 使用未定义变量不会产生任何语法错误。
1125
+
1126
+ ===== 优化排名语句
1127
+
1128
+ 使用用户自定义变量的一个重要特性是可以在给一个变量赋值的同时使用这个变量。
1129
+
1130
+ .使用变量显示行号
1131
+ [source,sql]
1132
+ ----
1133
+ SET @rownum := 0;
1134
+ SELECT
1135
+ actor_id,
1136
+ @rownum := @rownum + 1 AS rownum
1137
+ FROM actor
1138
+ LIMIT 3;
1139
+ ----
1140
+
1141
+ .使用变量排序,相同数量排名也相同
1142
+ [source,sql]
1143
+ ----
1144
+ SET @curr_cnt := 0, @prev_cnt := 0, @rank := 0;
1145
+
1146
+ SELECT
1147
+ actor_id,
1148
+ @curr_cnt := cnt AS cnt,
1149
+ @rank := if(@prev_cnt <> @curr_cnt, @rank + 1, @rank) AS rank,
1150
+ @prev_cnt := @curr_cnt AS dummy
1151
+ FROM (
1152
+ SELECT
1153
+ actor_id,
1154
+ COUNT(*) AS cnt
1155
+ FROM film_actor
1156
+ GROUP BY actor_id
1157
+ ORDER BY cnt DESC
1158
+ LIMIT 10
1159
+ ) AS der;
1160
+ ----
1161
+
1162
+ ===== 避免重复查询刚刚更新的数据
1163
+
1164
+ [source,sql]
1165
+ ----
1166
+ -- 根据上下文推断的建表语句
1167
+ DROP TABLE IF EXISTS tbl;
1168
+ CREATE TABLE tbl (
1169
+ id INTEGER AUTO_INCREMENT PRIMARY KEY,
1170
+ lastupdated TIMESTAMP
1171
+ );
1172
+
1173
+ -- 常规做法
1174
+ UPDATE tbl SET tbl.lastupdated = NOW() WHERE id = 1;
1175
+ SELECT lastupdated FROM tbl WHERE id = 1;
1176
+
1177
+ -- 使用变量,无须访问数据表,更高效
1178
+ UPDATE tbl SET tbl.lastupdated = NOW() WHERE id = 1 AND @now := NOW();
1179
+ SELECT @now;
1180
+ ----
1181
+
1182
+ ===== 确定取值的顺序
1183
+
1184
+ 使用用户自定义变量的一个最常见的问题是没有注意到在赋值和读取变量的时候可能是在查询的不同阶段。例如,在 `SELECT` 中定义,在 `WHERE` 中使用。
1185
+
1186
+ 解决这个问题的办法是让变量的赋值和取值发生在执行查询的同一阶段。
1187
+
1188
+ [source,sql]
1189
+ ----
1190
+ SET @rownum := 0;
1191
+ SELECT
1192
+ actor_id,
1193
+ @rownum AS rownum
1194
+ FROM actor
1195
+ WHERE (@rownum := @rownum + 1) <= 1;
1196
+ ----
1197
+
1198
+ 一个技巧:将赋值语句放到 `LEAST()` 函数中,这样就可以在完全不改变顺序的时候完成赋值操作。
1199
+
1200
+ [source,sql]
1201
+ ----
1202
+ SET @rownum := 0;
1203
+ SELECT
1204
+ actor_id,
1205
+ first_name,
1206
+ @rownum AS rownum
1207
+ FROM actor
1208
+ WHERE @rownum <= 1
1209
+ ORDER BY first_name, LEAST(0, @rownum := @rownum + 1);
1210
+ ----
1211
+
1212
+ ===== 编写偷懒的 `UNION`
1213
+
1214
+ 将用户分为热门用户和归档用不。查询用户时,热门用户中查不出来才去查归档用户,避免不必要的 `UNION` 子查询。
1215
+
1216
+ [source,sql]
1217
+ ----
1218
+ -- 建表语句是根据上下文推断的
1219
+ DROP TABLE IF EXISTS users;
1220
+ CREATE TABLE users (
1221
+ id INTEGER AUTO_INCREMENT PRIMARY KEY
1222
+ );
1223
+ DROP TABLE IF EXISTS users_archived;
1224
+ CREATE TABLE users_archived (
1225
+ id INTEGER AUTO_INCREMENT PRIMARY KEY
1226
+ );
1227
+
1228
+ -- 查询用户,热门用户中查不出来则查归档用户
1229
+ SELECT
1230
+ greatest(@found := -1, id) AS id,
1231
+ 'users' AS which_tbl
1232
+ FROM users
1233
+ WHERE id = 1
1234
+
1235
+ UNION ALL
1236
+
1237
+ SELECT
1238
+ id,
1239
+ 'users_archived' AS which_tbl
1240
+ FROM users_archived
1241
+ WHERE id = 1 AND @found IS NULL
1242
+
1243
+ UNION ALL
1244
+ -- 将变量充值,避免影响下次查询
1245
+ SELECT
1246
+ 1,
1247
+ 'reset'
1248
+ FROM dual
1249
+ WHERE (@found := NULL) IS NOT NULL;
1250
+ ----
1251
+
1252
+ ===== 用户自定义变量的其他用处
1253
+
1254
+ 在任何类型的 SQL 语句中都可以对变量进行赋值。
1255
+
1256
+ 一些典型的使用场景:
1257
+
1258
+ * 查询运行时计算总数和平均值。
1259
+ * 模拟 `GROUP` 语句中的函数 `FIRST()` 和 `LAST()`。
1260
+ * 对大量数据做一些数据计算。
1261
+ * 计算一个大表的 MD5 散列值。
1262
+ * 编写一个样本处理函数,当样本中的数值超过某个边界值的时候将其变成0。
1263
+ * 模拟读/写游标。
1264
+ * 在 `SHOW` 语句的 `WHERE` 子句中加入变量值。
1265
+
1266
+ 推荐阅读 https://book.douban.com/subject/26665768/[SQL and Relational Theory],改变对 SQL 语句的认识。
1112
1267
1113
1268
=== 案例学习
1114
1269
0 commit comments