MySQL的exists

相较于 in,exists 其实没有特别高频的使用。exists 语句并不那么容易读。今天我们来使用一下:

mock data

CREATE TABLE `users` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `status` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


CREATE TABLE `user_logs` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int unsigned NOT NULL,
  `content` varchar(255) NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

插入测试数据:

INSERT INTO `users`(id, name) VALUES(1, 'sai');
INSERT INTO `users`(id, name) VALUES(2, 'os');
INSERT INTO `users`(id, name) VALUES(3, 'jack');
INSERT INTO `users`(id, name) VALUES(4, 'kai');
INSERT INTO `users`(id, name) VALUES(5, 'tom');
INSERT INTO `users`(id, name) VALUES(6, 'peter');
INSERT INTO `users`(id, name) VALUES(7, 'li');
INSERT INTO `users`(id, name) VALUES(8, 'bob');
INSERT INTO `users`(id, name) VALUES(9, 'wuliuqi');
INSERT INTO `users`(id, name) VALUES(10, 'awesome');
INSERT INTO `user_logs`(user_id, content) VALUES(1, 'create post');
INSERT INTO `user_logs`(user_id, content) VALUES(1, 'delete post: 1');
INSERT INTO `user_logs`(user_id, content) VALUES(1, 'delete post: 55');
INSERT INTO `user_logs`(user_id, content) VALUES(2, 'delete post: 23');
INSERT INTO `user_logs`(user_id, content) VALUES(3, 'delete post:34');
INSERT INTO `user_logs`(user_id, content) VALUES(4, 'delete post:40');
INSERT INTO `user_logs`(user_id, content) VALUES(4, 'add post');
INSERT INTO `user_logs`(user_id, content) VALUES(4, 'delete post:99');
INSERT INTO `user_logs`(user_id, content) VALUES(4, 'delete post:140');

判断是否存在

我们使用 exists 判断某条记录是否存在:

mysql> select exists (select * from `users` where id = 1);
+---------------------------------------------+
| exists (select * from `users` where id = 1) |
+---------------------------------------------+
|                                           1 |
+---------------------------------------------+
1 row in set (0.00 sec)

mysql> select exists (select * from `users` where id = 13);
+----------------------------------------------+
| exists (select * from `users` where id = 13) |
+----------------------------------------------+
|                                            0 |
+----------------------------------------------+
1 row in set (0.00 sec)

子查询

exists 更多用于子查询:

SELECT * FROM `user_logs` 
WHERE EXISTS (
    SELECT * FROM `users` WHERE `users`.id = `user_logs`.user_id and `users`.id = 1
);

你知道会输出什么吗?

当我第一次看到这个语句时是这么理解的, where 后面的是1,所以会列出所有的 user_logs

显然我的理解是:

SELECT * FROM `user_logs` 
WHERE 1;

但输出打脸了 😢

+----+---------+-----------------+---------------------+
| id | user_id | content         | created_at          |
+----+---------+-----------------+---------------------+
|  1 |       1 | create post     | 2023-02-14 08:54:59 |
|  2 |       1 | delete post: 1  | 2023-02-14 08:54:59 |
|  3 |       1 | delete post: 55 | 2023-02-14 08:54:59 |
+----+---------+-----------------+---------------------+
3 rows in set (0.00 sec)

其实上面的语句与下面的语句结果一致的。

SELECT * FROM `user_logs` 
WHERE user_id in (
    SELECT id FROM `users` WHERE `users`.id = 1
);
SELECT `user_logs`.* FROM `user_logs` 
LEFT JOIN `users` ON `users`.id = `user_logs`.user_id WHERE `users`.id = 1;

in 还是 exists

对于很多博客中提到的 in 适合于外表大而子查询表小的情况,exists 适合于外表小而子查询表大的情况,其实并不准确。MySQL 手册中提到,如果没有使用 materialization ,优化器又是会重写查询,即可能会将 in 转成 exists。那么这种情况性能其实是没什么差别的。但是给大表加索引一般是有效的。

参考


MySQL的exists
https://blog.puresai.com/2023/02/13/473/
作者
puresai
许可协议