Mysql系列:Sql语句执行过程和事务

前言

        如果一个后端开发人员经常要与mysql打交道的话,那么写Sql语句在所难免,如果不了解一个Sql的执行过程,那可能会写出一个很烂的Sql语句,导致慢查询,严重影响服务器的性能。

        对于Mqsql中的事务,经常听到ACID隔离级别的概念,在下文中会详细解释下这些概念,并通过实际例子来验证隔离不同的隔离级别对事务的影响。


目录

  • Sql执行过程
    Mysql系列:Sql语句执行过程和事务_第1张图片
  • 事务
    Mysql系列:Sql语句执行过程和事务_第2张图片

Sql执行过程

  1. 构建三张表:
CREATE TABLE `testtable0` (
  `id` int(11) NOT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `testtable1` (
  `id` int(11) NOT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `testtable2` (
  `id` int(11) NOT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
  1. 执行如下sql:
select t0.age, sum(t0.id) count from testtable0 t0 
LEFT JOIN testtable1 t1 ON t0.age = t1.age LEFT JOIN 
testtable2 t2  ON t0.age = t2.age WHERE t0.age < 20
GROUP BY t0.age HAVING t0.age != 19;
  1. 根据如上表和sql进行分析:
    注:三张表中的所有数据:

    按照顺序分别为testtable1、testtable2、testtable0中的所有数据
    Mysql系列:Sql语句执行过程和事务_第3张图片Mysql系列:Sql语句执行过程和事务_第4张图片Mysql系列:Sql语句执行过程和事务_第5张图片

(1)首先将testtable0和testtable1进行笛卡尔积操作,产生虚拟表VT1,结构如下:
Mysql系列:Sql语句执行过程和事务_第6张图片
共12条记录,testtable0中有4条数据,而testtable1中有3条数据,所有会产生4 x 3 = 12条数据。
(2)根据VT1表中的结果进行ON t0.age = t1.age操作,会得到如下表结构:

因为ON不能单独出现,基本需要和一些关键字配合使用,比如和JOIN,直接执行ON类似于JOIN前面不跟LEFT、RIGHT之类的限定。例如:
select * from testtable0 t0 JOIN testtable1 t1 ON t0.age = t1.age ;

根据虚拟表V1可知,12条记录中不存在t0.age = t1.age的情况,所以执行完ON语句后会生成一个为空的虚拟表VT2。
空数据
(3)然后执行LEFT JOIN操作,即保留testtable0中未匹配的行作为外部行添加到VT2中,有VT2可知,testtable0中所有的行都没有匹配,所有直接将testtable0中的所有行添加到VT2中,生成VT3,结构如下:
Mysql系列:Sql语句执行过程和事务_第7张图片
(4)接着将VT3和testtable2进行笛卡尔积操作,产生虚拟表VT4,结构如下:
Mysql系列:Sql语句执行过程和事务_第8张图片
因为VT3中有4条数据,testtable2中有2条数据,所有笛卡尔积操作后会产生4 x 2 = 8条数据。

(5) 接着执行ON t0.age = t2.age操作,产生虚拟表VT5,结构如下:
Mysql系列:Sql语句执行过程和事务_第9张图片
(6)接着执行第二个LEFT JOIN操作,将VT4表中未匹配的行加入到VT5中,生成虚拟表VT6,结构如下:
Mysql系列:Sql语句执行过程和事务_第10张图片
(7)接着基于虚拟表VT6执行where t0.age < 20的操作,产生虚拟表VT7结构如下:
Mysql系列:Sql语句执行过程和事务_第11张图片
(8)接着基于VT7执行Group by t0.age操作,会进行分组操作,在后面,每一个分组只能作为一行记录展示,所有执行select时需要选择合适的展示项。经过Having操作后,会将VT7中的三条记录分为两组,t0.age = 18的两条记录分为一组,t0.age=19的一条记录作为一组,生成对应的虚拟表VT8。
(9)基于VT8执行Having t0.age != 19的操作,不满足Having语句的分组将会被丢弃调。所有t0.age = 19的分组会被丢弃调,然后产生虚拟表VT9。
(10)然后基于VT9执行select操作,选择指定的列,产生最终的表结构如下:
最终表结构
4. 基于上面的分析,然后大家自己本地训练下,相信对于sql的执行过程会有一个清楚的认识。

事务

  • 原子性(atomicity)
    一个事务必须视为一个不可分割的最小工作单元,整个事务的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。

对于事务的原子性,只要你写代码能满足这个特性,你就能保证原子性,比如下一个订单,在提单前已经扣了优惠券、扣了小金库的钱,如果提单失败后,如果你能保证优惠券、小金库等操作一定能够回滚成功,你就可以将其任务满足原子性。

  • 一致性(consistency)
    数据库总是从一个一致性状态转换到另一个一致性状态,就是提交一个事务之前,如果提交失败,事务之中所做的改变必须回滚到前一个状态,即事务完成才会从一个一致性状态转移到另一个一致性状态,不会有中间状态。
    Mysql系列:Sql语句执行过程和事务_第12张图片

  • 隔离性(isolation)
    通常来说一个事务所做的修改在最终提交前,对其他事务是不可见的。但是呢,在后面讨论隔离级别的时候,会发现有的隔离级别,当前未提交的事务的修改在其他事务之中也是可见的。

  • 持久性
    一旦事务提交,其所做的修改会永久保存在数据库中,此时即使数据库崩溃,修改的数据也不会丢失。

隔离级别

  • Read UNCOMMITTED(未提交读)
    在此级别中,事务中所做的修改即使没有提交,对其他事务也是可见的。事务读取到未提交的数据,称之为脏读(Dirty Read)。在性能上这个隔离级别并不会比其他隔离级别性能好太多,而且这个隔离级别会产生很多问题,除非有必要,不要使用这个隔离级别。

  • READ COMMITTED(提交读)
    这个隔离级别满足隔离性的简单定义:一个事务开始时,只能看见已经提交事务所做的修改。换句话说,一个事务从开始到提交前,所做的任何修改对其他事务是不可见的。这个隔离级别也叫作不可重复读(norepeatable read),因为两次同样的查询,可能会得到不一样的结果。

  • REPEATABLE READ(可重复读)
    该隔离级别保证了在同一个事务中多次读取同样的记录结果是一致的,但是无法解决幻读的问题,如果某一个事务读取一个范围内的数据时,其他事务在当前范围内又插入了数据,再次读取时会产生幻行。

  • SERIALIZABLE(可串行化)
    此隔离级别是最高的隔离级别,通过强制事务串行执行,避免了幻读的问题,此隔离级别会在读取的每一行数据上都加锁,可能会导致大量的锁竞争和锁争用问题。

下面也是通过例子演示这四种隔离级别的情况。

  1. 演示脏读的问题,需要将隔离级别设置为Read Uncommitted隔离级别。
  • 开启两个会话,将两个会话的隔离级别设置为Read Uncommitted;
    Mysql系列:Sql语句执行过程和事务_第13张图片
    Mysql系列:Sql语句执行过程和事务_第14张图片
    由上图可知,在一个记录修改更新当前记录数据后,事务并没有提交,在另一个事务中就看到为提交的数据了,也就是我们所说的脏读。
  1. 对于不可重复读问题,需要将隔离级别设置为Read Committed,这种隔离级别不会产生脏读的问题,但是同一条记录的查询可能结果不一致。
    Mysql系列:Sql语句执行过程和事务_第15张图片
    可以看到当隔离级别设置为Read Committed后,在一个事务中,一个sql的查询结果在两次是不相同的,这就是所说的不可重复读现象。

  2. 为了解决不可重复读问题,我们把隔离级别设置为REPEATABLE READ,看看在一个事务中执行相同的sql是不是结果一致。
    Mysql系列:Sql语句执行过程和事务_第16张图片
    从上图看出,将隔离级别改为可重复读后,虽然数据已经变了,但是在一个事务中的两次查询结果是一致的。

  3. 幻读,可重复读虽然解决了不可重复读问题,但是还会遇到幻读问题,即在两次范围查找时,结果不一致。范围查询是关键,相当于虽然已经插入了一条数据,在一个事务中却查不到这条数据,而在事务结束后再次查询才能查到这条数据。
    Mysql系列:Sql语句执行过程和事务_第17张图片

总结

  1. 首先主要介绍了Sql的执行过程,熟悉Sql的具体执行过程,才不会写出很糟糕的sql语句。
  2. 接着介绍了mysql的事务特性和隔离级别,分析了在不同的隔离级别会遇到什么问题。

你可能感兴趣的:(mysql)