Mysql中用exists代替in

exists对外表用loop逐条查询,每次查询都会查看exists的条件语句,当 exists里的条件语句能够返回记录行时(无论记录行是的多少,只要能返回),条件就为真,返回当前loop到的这条记录,反之如果exists里的条 件语句不能返回记录行,则当前loop到的这条记录被丢弃,exists的条件就像一个bool条件,当能返回结果集则为true,不能返回结果集则为 false

如下:

select * from user where exists (select 1);

对user表的记录逐条取出,由于子条件中的select 1永远能返回记录行,那么user表的所有记录都将被加入结果集,所以与 select * from user;是一样的

又如下

select * from user where exists (select * from user where userId = 0);

可以知道对user表进行loop时,检查条件语句(select * from user where userId = 0),由于userId永远不为0,所以条件语句永远返回空集,条件永远为false,那么user表的所有记录都将被丢弃

not exists与exists相反,也就是当exists条件有结果集返回时,loop到的记录将被丢弃,否则将loop到的记录加入结果集

总的来说,如果A表有n条记录,那么exists查询就是将这n条记录逐条取出,然后判断n遍exists条件 

 

 

in查询相当于多个or条件的叠加,这个比较好理解,比如下面的查询

select * from user where userId in (1, 2, 3);

等效于

select * from user where userId = 1 or userId = 2 or userId = 3;

not in与in相反,如下

select * from user where userId not in (1, 2, 3);

等效于

select * from user where userId != 1 and userId != 2 and userId != 3;

总的来说,in查询就是先将子查询条件的记录全都查出来,假设结果集为B,共有m条记录,然后在将子查询条件的结果集分解成m个,再进行m次查询

 

值得一提的是,in查询的子条件返回结果必须只有一个字段,例如

select * from user where userId in (select id from B);

而不能是

select * from user where userId in (select id, age from B);

而exists就没有这个限制

 

下面来考虑exists和in的性能

考虑如下SQL语句

1: select * from A where exists (select * from B where B.id = A.id);

2: select * from A where A.id in (select id from B);

 

查询1.可以转化以下伪代码,便于理解

for ($i = 0; $i < count(A); $i++) {

  $a = get_record(A, $i); #从A表逐条获取记录

  if (B.id = $a[id]) #如果子条件成立

    $result[] = $a;

}

return $result;

大概就是这么个意思,其实可以看到,查询1主要是用到了B表的索引,A表如何对查询的效率影响应该不大

 

假设B表的所有id为1,2,3,查询2可以转换为

select * from A where A.id = 1 or A.id = 2 or A.id = 3;

这个好理解了,这里主要是用到了A的索引,B表如何对查询影响不大

 

下面再看not exists 和 not in

1. select * from A where not exists (select * from B where B.id = A.id);

2. select * from A where A.id not in (select id from B);

看查询1,还是和上面一样,用了B的索引

而对于查询2,可以转化成如下语句

select * from A where A.id != 1 and A.id != 2 and A.id != 3;

可以知道not in是个范围查询,这种!=的范围查询无法使用任何索引,等于说A表的每条记录,都要在B表里遍历一次,查看B表里是否存在这条记录

故not exists比not in效率高

 

mysql中的in语句是把外表和内表作hash 连接,而exists语句是对外表作loop循环,每次loop循环再对内表进行查询。一直大家都认为exists比in语句的效率要高,这种说法其实是不准确的。这个是要区分环境的。
 

如果查询的两个表大小相当,那么用in和exists差别不大。 
如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in: 
例如:表A(小表),表B(大表)
 
1:
select * from A where cc in (select cc from B) 效率低,用到了A表上cc列的索引;
 
select * from A where exists(select cc from B where cc=A.cc) 效率高,用到了B表上cc列的索引。 
相反的
 
2:
select * from B where cc in (select cc from A) 效率高,用到了B表上cc列的索引;
 
select * from B where exists(select cc from A where cc=B.cc) 效率低,用到了A表上cc列的索引。
 
 
not in 和not exists如果查询语句使用了not in 那么内外表都进行全表扫描,没有用到索引;而not extsts 的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快。 
in 与 =的区别 
select name from student where name in ('zhang','wang','li','zhao'); 
与 
select name from student where name='zhang' or name='li' or name='wang' or name='zhao' 
的结果是相同的。

转载于:https://www.cnblogs.com/xianlei/p/8862313.html

<think>我们正在解决的是MySQL存储过程或函数中出现的错误提示:'unlock is not allowed in stored procedures' 根据错误信息,可知在存储过程中不允许执行UNLOCK操作。在MySQL中,存储过程和函数有特定的锁操作限制。 参考之前对话中关于锁的讨论(特别是共享锁部分),我们知道在存储过程中可以使用LOCK TABLES和UNLOCK TABLES来管理表锁。但是,根据MySQL官方文档,存储过程和函数中不允许使用UNLOCK TABLES,除非该存储过程或函数中已经执行了LOCK TABLES。 具体规则如下: 1. 在存储过程中,可以使用LOCK TABLES来锁定表,但必须在同一个存储过程中使用UNLOCK TABLES来解锁(在存储过程结束前解锁)。 2. 在存储函数中,不允许执行LOCK TABLES或UNLOCK TABLES语句。 因此,出现这个错误的原因可能是: 情况1:在存储函数中使用了UNLOCK TABLES(或LOCK TABLES),这是不允许的。 情况2:在存储过程中,UNLOCK TABLES出现在LOCK TABLES之前,或者没有对应的LOCK TABLES。 解决方案: 情况1:如果是存储函数,则不能使用显式的表锁。考虑使用其他方式避免锁定,或者改用存储过程。 情况2:如果是存储过程,确保: - 在LOCK TABLES之后使用UNLOCK TABLES。 - 在同一个存储过程中,每一个LOCK TABLES都必须有对应的UNLOCK TABLES(不能嵌套,除非多次锁定同一个表,但MySQL会合并锁请求)。 - 注意:不能在存储过程中解锁一个未在该存储过程中锁定的表。 示例代码(存储过程): ```sql CREATE PROCEDURE example_procedure() BEGIN -- 必须先锁定表 LOCK TABLES table1 READ, table2 WRITE; -- 执行一些操作... -- 在结束前解锁 UNLOCK TABLES; END; ``` 但是,如果我们在存储过程中使用了LOCK TABLES,然后在未解锁的情况下再次锁定其他表,或者忘记解锁,都可能导致问题。 另外,在存储过程中使用表锁可能会导致并发性能下降,因此建议: - 尽量使用行级锁(通过SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE)来减少锁的粒度。 - 确保事务尽可能短,避免长时间持有锁。 注意:在存储过程中,使用行级锁(InnoDB)必须在事务中,并且事务的提交或回滚会释放所有的行锁。 因此,如果我们遇到在存储函数中需要锁的情况,由于存储函数不允许使用LOCK TABLES/UNLOCK TABLES,我们可以考虑: - 将函数改为存储过程(如果允许)。 - 使用GET_LOCK()和RELEASE_LOCK()函数来管理命名锁(这些锁是用户级别的,不是表锁,但可以用于同步)。 例如: SELECT GET_LOCK('my_lock', 10); -- 获取名为'my_lock'的锁,等待10秒 ... 执行操作 ... SELECT RELEASE_LOCK('my_lock'); - 或者,在存储函数中避免使用锁,通过调整业务逻辑来实现。 总结步骤: 1. 确认错误发生的上下文:是在存储过程还是存储函数中? 2. 如果是存储函数: - 移除LOCK TABLES和UNLOCK TABLES语句。 - 考虑使用命名锁(GET_LOCK/RELEASE_LOCK)或者改用存储过程。 3. 如果是存储过程: - 检查LOCK TABLES和UNLOCK TABLES是否成对出现,且UNLOCK TABLES之前必须有LOCK TABLES。 - 确保没有在未锁定的情况下解锁。 - 确保在存储过程的所有出口(包括异常处理)都解锁。 4. 考虑使用事务和行级锁(InnoDB引擎)代替表锁: - 在存储过程中,使用START TRANSACTION开始事务。 - 使用SELECT ... FOR UPDATE(排他锁)或SELECT ... LOCK IN SHARE MODE(共享锁)来锁定需要修改或读取的行。 - 在事务结束时(COMMIT或ROLLBACK)会自动释放锁。 示例(使用行级锁): ```sql CREATE PROCEDURE update_example() BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; -- 其他清理操作 END; START TRANSACTION; -- 对需要更新的行加排他锁 SELECT * FROM table1 WHERE id = 1 FOR UPDATE; -- 执行更新操作 UPDATE table1 SET column1 = 'new_value' WHERE id = 1; COMMIT; END; ``` 这样,我们就不需要显式的表锁,从而避免在存储过程中使用LOCK TABLES和UNLOCK TABLES。 因此,针对错误'unlock is not allowed in stored procedures',我们应根据具体情况调整代码结构,避免在存储过程中不匹配的锁操作,并在存储函数中完全避免表锁。 相关问题: 1. 如何在MySQL存储过程中正确使用表锁? 2. 存储函数中可以使用哪些锁机制? 3. 行级锁和表级锁在存储过程中的性能差异? 4. 如何利用MySQL的命名锁(GET_LOCK)实现并发控制? 5. 在存储过程中使用锁可能导致哪些常见问题? 注意:在存储过程中使用锁需要格外谨慎,避免死锁和长时间锁等待。 </think>### 解决 MySQL 存储过程中出现 'unlock is not allowed in stored procedures' 错误的方法 该错误表示在存储过程/函数中尝试执行了不允许的 `UNLOCK` 操作。根本原因是 **MySQL 不允许在存储函数中显式调用锁操作**(但存储过程有限支持)。以下是完整解决方案: --- #### 一、错误原因分析 1. **存储函数 vs 存储过程**区别: - ✅ 存储过程支持 `LOCK TABLES` 和 `UNLOCK TABLES` - ❌ 存储函数**禁止**任何显式锁操作(设计约束)[^1] ```sql CREATE FUNCTION demo_func() RETURNS INT BEGIN UNLOCK TABLES; -- 此处触发错误 RETURN 1; END; ``` 2. **事务锁与表锁混淆**: - 行级锁(`SELECT ... FOR UPDATE`)属于事务锁,在存储过程/函数中允许使用 - 表级锁(`LOCK TABLES/UNLOCK TABLES`)在存储函数中被禁止 --- #### 二、解决方案 ##### 方案1:将函数改为存储过程(推荐) ```sql -- 1. 删除原函数 DROP FUNCTION IF EXISTS your_function; -- 2. 改为存储过程 CREATE PROCEDURE your_procedure() BEGIN -- 允许使用表锁(需成对出现) LOCK TABLES table_name READ; -- 业务逻辑... UNLOCK TABLES; -- 此处不再报错 END; ``` ##### 方案2:改用事务锁替代表锁 ```sql CREATE FUNCTION your_function() RETURNS INT BEGIN DECLARE result INT; START TRANSACTION; -- 使用行级锁(允许在函数中使用) SELECT counter INTO result FROM counters WHERE id = 1 FOR UPDATE; UPDATE counters SET counter = result + 1 WHERE id = 1; COMMIT; RETURN result; END; ``` ##### 方案3:避免显式锁操作 使用乐观锁或原子操作: ```sql CREATE FUNCTION safe_update() RETURNS INT BEGIN -- 原子操作示例(无锁) UPDATE orders SET total = total + 100 WHERE order_id = 123; RETURN ROW_COUNT(); END; ``` --- #### 三、关键设计规范 1. **锁使用原则**: - 存储函数中只允许**隐式锁**(如事务中的 `FOR UPDATE`) - 表级锁必须在存储过程中**成对出现**: ```sql CREATE PROCEDURE valid_lock_demo() BEGIN LOCK TABLES t1 WRITE, t2 READ; -- 先锁定 -- 业务逻辑... UNLOCK TABLES; -- 后解锁 END; ``` 2. **事务边界控制**: ```mermaid graph LR A[开始事务] --> B[加行级锁] B --> C[业务操作] C --> D{成功?} D -->|是| E[提交] D -->|否| F[回滚] E & F --> G[释放锁] ``` --- #### 四、错误排查步骤 1. 检查对象类型: ```sql SELECT ROUTINE_TYPE FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'your_function'; -- 返回 FUNCTION 即说明问题 ``` 2. 搜索锁操作语句: ```sql SHOW CREATE FUNCTION your_function; -- 查找 LOCK/UNLOCK 关键字 ``` --- #### 五、最佳实践建议 1. 优先使用存储过程处理含锁逻辑 2. 在函数中用原子操作替代锁 3. 长事务拆分为短操作,减少锁持有时间 4. 使用监控工具检查锁争用: ```sql -- 查看当前锁状态 SELECT * FROM performance_schema.data_locks; ``` > **重要提示**:生产环境修改前务必在测试环境验证,避免锁升级导致死锁[^2]。 --- ### 相关问题 1. 如何在MySQL存储过程中正确实现行级锁? 2. 存储函数和存储过程的主要性能差异是什么? 3. 哪些情况下MySQL会触发隐式表锁定? 4. 如何监控MySQL中的锁等待情况? 5. 在读写分离架构中,存储函数的行为会有什么变化? [^1]: MySQL 官方文档明确规定存储函数中禁止显式锁操作 [^2]: 不规范的锁操作可能导致死锁和性能下降
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值