共计 1472 个字符,预计需要花费 4 分钟才能阅读完成。
导读 | 业务系统在运行的时候,往往有很多线程同时在操作数据库,MySQL 也需要多线程的处理多个请求,那么每个事务里的多个 SQL 语句是如何执行的呢?基本都是从磁盘加载数据页到 Buffer Pool 的缓存页里去,然后更新 Buffer Pool 里的缓存页,同时记录 redo log 和 undo log。 |
多个线程并发执行的时候,会有一些问题:
多个事务并发执行,可能会对缓存页里的同一行数据进行更新,这种冲突怎么解决?
有的事务在对一行数据进行更新,另一个事务在对这行数据进行查询,这个冲突怎么解决?
接下来要讲的内容,主要包括多事务并发运行存在的问题、MySQL 的事务隔离级别、MVCC 多版本控制、锁。
事务 1 读取了事务 2 更新的数据,然后事务 2 回滚操作,那么事务 1 读取到的数据就是脏数据。
例如:
张三的工资是 8000,现在领导要给他涨工资到 10000。
事务 1 把他的工资改为 10000,但事务还没提交。
事务 2 正在读取张三的工资,独到的是 10000。
事务 1 此时回滚了,张三的工资又变成 8000 了。
事务 2 读取到张三的工资 10000 为脏数据,事务 2 做了一次脏读。
事务 1 多次读取同一数据,事务 2 在事务 1 多次读取的过程中,对数据作了更新并提交,导致事务 1 多次读取同一数据时,结果不一致。
例如:
事务 1,读取到张三的工资 8000。
事务 2,把张三的工资改为 10000,提交事务。
事务 1,再次读取张三的工资,此时工资为 10000。
在一个事务中前后两次读取同一个数据的结果不一样,就是不可重复读。
一个事务用一样的 SQL 多次查询,每次查询发现查到一些之前没看到过的数据。
比如,目前公司工资为 8000 的人有 10 人。
事务 1,读取所有工资为 8000 的人数为 10 人。
事务 2,插入一条工资为 10000 的记录。
事务 1 再次读取工资为 8000 的人,为 11 人。
此时,事务 1 出现幻觉似的,同样的 SQL 查询语句,第一次查出来 10 人,第二次查出来 11 人。
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
脏读、不可重复读、幻读,数据库并发执行,每个线程可能会开启一个事务,每个事务都会执行 crud 操作。
数据库并发的执行多个事务,多个事务并发的对缓存页里的同一批数据进行 crud,就可能导致脏读、不可重复读、幻读这些问题。
所以,这些问题的本质是,数据库多事务并发问题,为了解决这些问题,数据库设计了事务隔离级别、MVCC 多版本控制、锁机制。
SQL 标准中定义了 4 种事务隔离级别,就是说事务并发运行的时候,互相是如何隔离的,MySQL 默认是可重复读 (RR)。
不同的隔离级别是可以避免不同的事务并发问题的。
read-uncommitted(RU),可能会发生脏读、不可重复读、幻读。
read-committed(RC),不会发生脏读,但是会发生不可重复读、幻读
也就是说,事务没提交的情况下修改的值,你是读不到的。但是,一旦事务提交了,你事务就能读到,所以可能你多次读到的值不一样。
repeatable-read(RR),MySQL 默认的事务隔离级别,做了大量复杂的工作,解决了脏读、不可重复读、幻读问题,一般生成环境使用这个隔离级别就可以了。
serializable,事务串行执行,根本不会并发执行,所以不会有脏读、不可重复读、幻读这些问题。但缺点也非常明显,就是并发太差了。一般生产环境也不会使用