update防止锁全表记录
悲观者从机会中看到困难。乐观者从困难中看到机会。
——温斯顿·丘吉尔
InnoDB 存储引擎的默认事务隔离级别是「可重复读」,但是在这个隔离级别下,在多个事务并发的时候,会出现幻读的问题,所谓的幻读是指在同一事务下,连续执行两次同样的查询语句,第二次的查询语句可能会返回之前不存在的行。
因此 InnoDB 存储引擎自己实现了行锁,通过 next-key 锁(记录锁和间隙锁的组合)来锁住记录本身和记录之间的“间隙",防止其他事务在这个记录之间插入新的记录,从而避免了幻读现象。
当我们执行 update 语句时,实际上是会对记录加独占锁(X 锁)的,如果其他事务对持有独占锁的记录进行修改时是会被阻塞的。另外,这个锁并不是执行完 update 语句就会释放的,而是会等事务结束时才会释放。
在InnoDB 事务中,对记录加锁带基本单位是 next-key 锁、但是会因为一些条件会退化成间隙锁,或者记录锁。加锁的位置准确的说,锁是加在索引上的而非行上。
比如,在 update 语句的 where 条件使用了唯一索引,那么 next-key 锁会退化成记录锁,也就是只会给一行记录加锁。
那update语句的where带上索引就能避免全表记录加锁了吗?
并不是
关键还得看这条语句在执行过程中,优化器最终选择的是索引扫描,还是全表扫描,如果走了全表扫描,就会对全表的记录加锁了。
如何避免这种事故的发生?
我们可以将MySQL里的 sql_safe_updates
参数设置为1,开启安全更新模式。
官方的解释: lf set to 1, MySQL aborts UPDATE or DELETE statements that do not use a key inthe WHERE clause or a LlMlT clause. (Specifically, UPDATE statements must have a WHEREclause that uses a key or a LlMlT clause, or both. DELETE statements must have both.) Thismakes it possible to catch UPDATE or DELETE statements where keys are not used properly andthat would probably change or delete a large number of rows. The default value is 0.
大致的意思是,当 sql_safe_updates 设置为 1时。
update 语句必须满足如下条件之一才能执行成功:
- 使用 where,并且 where 条件中必须有索引列;
- 使用 limit;
- 同时使用 where 和 limit,此时 where 条件中可以没有索引列;
delete 语句必须满足以下条件能执行成功:
- 同时使用 where 和 limit,此时 where 条件中可以没有索引列;
如果 where 条件带上了索引列,但是优化器最终扫描选择的是全表,而不是索引的话,我们可以使用force index([index_name])
可以告诉优化器使用哪个索引,以此避免有几率锁全表带来的隐患。