mysql的隔离级别

环境mysql8.0
1、事务的基本概念
事务(Transaction)是数据库一个逻辑操作单元,要么全部执行,要么全部不执行,用来保证数据的一致性和完整性。在Spring和Java中,事务管理是保证业务操作正确性的重要机制。
事务的4个核心机制:ACID。
原子性(Atomicity):事务中的操作是不可分割的最小工作单位,执行要么全部成功要么全部失败,
举例: 用户 A 向用户 B 转账 100 元,涉及两步:
从 A 账户扣减 100 元;
向 B 账户增加 100 元;
如果中途某一步失败,整个事务就会回滚,A 的钱不会被扣。
一致性(Consistency):事务前后数据必须保持一致,数据库从一个合法状态转换为另一个合法状态。
举例: 转账前,A+B 总金额为 1000,事务完成后也必须为 1000。不能凭空多出或少掉钱。
隔离性(Isolation):并发事务之间互不干扰,一个事务不能看到另一个事务的中间结果。
持久性(Durability):一旦事务提交,其结果将永久保留在数据库中,哪怕系统崩溃。
举例: 转账成功后,即使服务器宕机,账户金额变动也必须保留。
2、隔离级别
read_uncommitted(读未提交、幻读、脏读)
read_committed (读已提交、幻读)
repeatable_read (可重复读、幻读)
可重复读:在一个事务中,重复读取某行数据的结果始终一致,即使其他事务已经提交了对该行的修改。
serializable 串行化(隔离级别最高,安全最高,性能最差)
查看mysql的隔离级别:
全局默认隔离级别:select @@global.transaction_isolation;
查看当前窗口的隔离级别:select @@transaction_isolation;
修改当前窗口的隔离级别为serializable:set session transaction isolation level serializable;
a、测试serializable的隔离
打开两个窗口
第一个窗口的隔离级别是:repeatable_read
仅执行窗口中红框的语句。不要commit
image
第二个窗口的隔离级别是:serializable
仅执行窗口中1红框的语句。不要commit,可以看到2这块一直是正在处理,因为当前窗口的隔离级别是serializable,第一个窗口没有commit,所以当前窗口一直在等待前面的事务提交。
如果第一个窗口commit,第二个窗口会立即执行。
image
b、测试repeatable_read隔离
将第一个窗口的隔离级别改为repeatable read,set session transaction isolation level repeatable read;
执行红框中的两行sql,不要commit,查询到的数据是99
image
在第二个窗口执行红框的三行sql,然后再执行select * from user;可以看到money已经改成100了,
image
此时,你再执行第一个窗口的select * from user where username = 'zhangsan',可以看到一直是99.
这也就解释了可重复读,在同一个事务中,看不到别人的修改。除非你先将当前的事务commit,再次查询就可以看到100了。
b1、测试幻读(repeatable_read幻读的情况)
将user表的username设置为唯一索引(UNIQUE)
执行第一个窗口红框的两条sql内容
image
可以看到,数据库没有改内容。
在第二个窗口执行红框3行sql
image
再执行select * from user;可以看到wangwu已经写入数据库了。
此时,我再回到第一个窗口执行红框的insert语句,就会报错:1062 - Duplicate entry 'wangwu' for key 'user.1'
image
主要原因还是当前事务没有提交,看不到别人提交的数据。
另外在工作中,有时候在同一个事务或者同一个方法中,对写入的数据会立即查询并会更新的操作。比如:

  @Transactional
  public int insert2(int flag) {
      Demo demo = new Demo();
      demo.setName("spot");
      demo.setAge(20);
      demoDao.insert(demo);
      LambdaQueryWrapper<Demo> wrapper = new LambdaQueryWrapper<>();
      wrapper.eq(Demo::getName,"spot");
      Demo selectOne = demoDao.selectOne(wrapper);
      if(selectOne.getName().equals("spot")){
          UpdateWrapper updateWrapper = new UpdateWrapper();
          updateWrapper.set("name","option");
          updateWrapper.set("age",23);
          demoDao.update(null,updateWrapper);
      }
      return flag;
  }

当执行demoDao.insert(demo);时,写入了数据库,但是还没有提交,所以对其他事务而言,并不能看见,当前事务可见,所以在Demo selectOne = demoDao.selectOne(wrapper);执行时,可以查询出来。
c、测试read_committed隔离级别
将第一个窗口的隔离级别改为read committed:set session transaction isolation level read COMMITTED;
执行红框的2行sql,money是100
image
再执行第二个窗口的3行sql,此时,zhangsan的money已经改成101了
image
再回到窗口1,执行:select * from user where username = 'zhangsan'; 可以看到窗口1可以查询到zhangsan的money也是101
image
c1、测试幻读(read committed)
在窗口1执行红框的2行sql,不要committed,查询不到lisi的信息。
image
在窗口2执行2行sql,不要committed
image
再执行窗口1的sql:insert into user(username,money) values('lisi',1);此时sql一直是执行中,
image
如果将窗口1的commit执行,那么窗口2就会报错:1062 - Duplicate entry 'lisi' for key 'user.1'
d、测试read uncommitted:
read uncommitted就是读到了别的事务还未提交的数据。如果别的事务发生回滚,则数据就会有问题。
d1、测试read uncommitted幻读
将窗口1的隔离级别设置为read uncommitted:set session transaction isolation level read uncommitted;执行红框1的内容,没有zhaoliu的用户。
image
执行窗口2的红框内容,不要commit
image
再次执行窗口1的select * from user;可以看到zhaoliu已经写入数据库了
image
这时,执行delete from user where username = 'zhaoliu';就会看到一直是正在处理中。如果窗口1回滚,窗口2删除就会出错。
image

posted @ 2025-07-02 10:21  Charlie-Pang  阅读(322)  评论(0)    收藏  举报