mysql 事务 锁
1.PHP事务四大特性
1.1 原子性:事务被视为不可分割的最小操作单元,要么全部执行成功,要么全部执行失败回滚。这意味着在事务执行期间,如果有任何一条 SQL 查询失败,整个事务将会被回滚,以保持数据的一致性。
1.2 一致性:事务执行前后,数据库的状态应该保持一致。这意味着事务中的操作应该使数据库从一个一致的状态转移到另一个一致的状态,而不会破坏数据库的完整性和约束条件。
1.3 隔离性:多个事务并发执行时,每个事务的操作应该相互隔离,互不干扰。这意味着在并发环境下,一个事务的操作不应该影响其他事务的操作,从而保证每个事务都能得到正确的结果。
1.4 持久性:是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
2.laravel中使用事务
DB::beginTransaction();//开启事务 try { $row = LikesDetails::create(['article_id' => $article_id, 'review_id' => $review_id, 'member_id' => $member_id, 'update_time' => $time,'is_liked' => $is_liked, 'create_time' => $time]); $likes = Article::where(['id' => $article_id])->value('praise'); $likes += 1; Article::where(['id' => $article_id])->update(['praise' => $likes, 'update_time' => $time]); DB::commit();//提交事务 $message = '添加成功'; } catch (Exception $e) { DB::rollBack();//事务回滚 $message = '添加失败'; }
如果在事务的闭包中出现了异常,事务将会自动回滚。如果闭包执行成功,事务将会自动提交。在使用 transaction
方法时不需要手动回滚或提交
DB::transaction(function () { DB::update('update users set votes = 1'); DB::delete('delete from posts'); });
3、因为存在并发的事务,提升了性能,也会出现一些问题
脏读:一个事务读取到另一个事务未提交的数据就是脏读;
丢失修改:AB两个事务同时进行,A 事务读取a的值为20 然后进行加一结果为21,B事务读取a的值也为20然后也进行加一结果也是21,两个事务结束之后 最终的结果为21;丢失率先提交书事务的数据;
不可重复读:当一个事务对一个数据进行多次查询的时候;因为其他的事务进行了修改并且提交;会导致这个事务中多次读取的数据是不一致的。
幻行读:和不可重复读的场景类似,读取几行数据的时候比如范围查询 age<18,第一次读的时候有3行数据,然后其他事务插入了5行数据,那么这个事务再 进行查询的时候就会发现数据变多了,这就是幻行读;
4、SQL 的4类隔离级别
4.1、读未提交:最低的隔离级别 可以读未提交的数据,脏读,不可重复读换行读都可能会发生。
4.2、读已提价:允许读已提交的数据,避免了脏读的出现,但不可重复读和幻行读仍然会出现。
4.3、可重复读:对同一个字段的多次读取结果是一致的,除非自己修改的,避免了脏读和不可重复读
4.4、可串行化:最高的隔离级别,通过强制事务排序,使之不能相互冲突,从而解决幻读问题。(就是在每个读的数据行上加了共享锁),可能会出现大量 的超时现象和锁竞争。
5、锁机制
5.1、乐观锁:假设并发操作时不会发生冲突,只在提交事务时检查事务是否被其他事务修改过。常用与读多写少的场景。
5.2、悲观锁:假设并发操作时会有发生冲突,因此在操作期间持有锁来避免冲突。常用与写多读少的场景。悲观并发控制实际上是“先取锁再访问”的保守策略
悲观锁主要分为共享锁和排他锁 :
5.2.1 共享锁又称为 读锁 简称 S 锁;共享锁就是多个事务对于同一个数据可以共享一把锁,都能访问到数据但是只能读不能修改;
5.2.2 排它锁称为写锁,简称X锁,排它锁不能与其他所共存,如果一个事务获取了一个数据行的排它锁,其他事务就不能再获取该行的其他锁,包括 共享锁和排它锁。获取排它锁的事务可以对数据进行读取和修改。
begin; //1.查询出商品库存信息 select quantity from items where id=1 for update, //2.修改商品库存为2 update items set quantity=2 where id =1; //3.提交事务 commit;
使用 select ... for update 锁数据,需要注意锁的级别,MySQL InnoDB 默认行级锁。行级锁都是基于索引的,如果一条 SQL 语句
用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住
laravel 中使用乐观锁和悲观锁
DB::beginTransaction();//开启事务 try { LikesDetails::where(['id'=>2])->sharedLock()->value('remark');//加锁,锁定一行数据防止被修改直到失误被提交//适用于多读操作
LikesDetails::where(['id'=>2])->lockForUpdate()->value('remark');//避免其他连接对数据进行读取或写入操作//适用于多写操作//新的事务查询操作时需要前面的事务提交之后才能抓取到数据
LikesDetails::where(['id'=>2])->update(['remark'=>'悲观锁测试']);
sleep(30); DB::commit();//提交事务 }catch (Exception $e){ DB::rollBack();//事务回滚 }
普通查询没有锁机制 laravel在使用 lockForUpdate()时 仍然可以使用 select ... from table where ... 读取到数据