为什么 SELECT FOR UPDATE 只在事务中起作用
在MySQL中SELECT FOR UPDATE建议要在事务中运行,原因是当SELECT FOR UPDATE执行完之后,就释放锁了。其实查询出来的数据接下来还要更新,所以建议必须要在事务中运行,针对spring事务其实就是加注解@Transaction。在InnoDB中,每个查询都有效地在事务中运行。如果您没有显式启动事务(通过启动事务或通过将autocommit设置为off),则在查询运行后将提交每个事务。这意味着,如果您不在事务中,则在查询完成后将立即释放使用SELECT ... IN SHARE MODE获取的锁。没有什么可以阻止您这样做,只是在事务外部使用锁没有多大意义。因为这些锁定是为了确保您选择的值在要执行的后续查询之前不会更改(例如,如果您要基于另一个表中的值在一个表中插入/更新数据)。
注意 FOR UPDATE 仅适用于InnoDB,且必须在事务区块(BEGIN/COMMIT)中才能生效。 由于InnoDB 预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。
图二中显示一直转圈圈,说明上一个图中已经上了锁,第二图的更新必须要在图一结束之后才能进行更新,防止数据出现问题。
for update的使用场景
如果遇到存在高并发并且对于数据的准确性很有要求的场景,是需要了解和使用for update的。比如涉及到金钱、库存等。一般这些操作都是很长一串并且是开启事务的。如果库存刚开始读的时候是1,而立马另一个进程进行了update将库存更新为0了,而事务还没有结束,会将错的数据一直执行下去,就会有问题。所以需要for upate 进行数据加锁防止高并发时候数据出错。
记住一个原则:一锁二判三更新
排他锁的申请前提:没有线程对该结果集中的任何行数据使用排他锁或共享锁,否则申请会阻塞。for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。
总结:加索是针对针对索引项来实现的,否则是使用表锁。
1、InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
2、由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。
3、当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
4、即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
5、检索值的数据类型与索引字段不同,虽然MySQL能够进行数据类型转换,但却不会使用索引,从而导致InnoDB使用表锁。通过用explain检查两条SQL的执行计划,我们可以清楚地看到了这一点。