在进行 SQL 调优之前,可以先选择 DBMS 和数据表的设计方式。你能看到,不同的 DBMS 直接决定了后面的操作方式,数据表的设计方式也直接影响了后续的 SQL 查询语句。

另外,你可以把 SQL 查询优化分成两个部分,逻辑查询优化和物理查询优化。

虽然 SQL 查询优化的技术有很多,但是大方向上完全可以分成逻辑查询优化和物理查询优化两大块。逻辑查询优化就是通过 SQL 等价变换提升查询效率,直白一点就是说,换一种查询写法执行效率可能更高。物理查询优化则是通过索引和表连接方式等技术来进行优化,这里重点需要掌握索引的使用。

mysql插入前先检查是否存在 mysql先查询后修改_查询优化

设计范式

数据表设计的三种范式。关系型数据库的设计都是基于关系模型的,在关系模型中存在着 4 种键,这些键的核心作用就是标识。

在这些概念的基础上,又有 1NF,2NF 和 3NF。我们经常会与这三种范式打交道,利用它们建立冗余度小、结构合理的数据库。

有一点需要注意的是,这些范式只是提出了设计的标准,实际上设计数据表时,未必要符合这些原则。一方面是因为这些范式本身存在一些问题,可能会带来插入,更新,删除等的异常情况(这些会在下一讲举例说明),另一方面,它们也可能降低会查询的效率。这是为什么呢?因为范式等级越高,设计出来的数据表就越多,进行数据查询的时候就可能需要关联多张表,从而影响查询效率。

mysql插入前先检查是否存在 mysql先查询后修改_mysql插入前先检查是否存在_02

mysql插入前先检查是否存在 mysql先查询后修改_执行计划_03

乐观锁(Optimistic Locking)认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,也就是不采用数据库自身的锁机制,而是通过程序来实现。在程序上,我们可以采用版本号机制或者时间戳机制实现。

悲观锁(Pessimistic Locking)也是一种思想,对数据被其他事务的修改持保守态度,会通过数据库自身的锁机制来实现,从而保证数据操作的排它性。

mysql插入前先检查是否存在 mysql先查询后修改_查询优化_04

mysql插入前先检查是否存在 mysql先查询后修改_SQL_05

可以采取一些方法避免死锁的发生:

  1. 如果事务涉及多个表,操作比较复杂,那么可以尽量一次锁定所有的资源,而不是逐步来获取,这样可以减少死锁发生的概率;
  2. 如果事务需要更新数据表中的大部分数据,数据表又比较大,这时可以采用锁升级的方式,比如将行级锁升级为表级锁,从而减少死锁产生的概率;
  3. 不同事务并发读写多张数据表,可以约定访问表的顺序,采用相同的顺序降低死锁发生的概率。

当然在数据库中,也有一些情况是不会发生死锁的,比如采用乐观锁的方式。另外在 MySQL MyISAM 存储引擎中也不会出现死锁,这是因为 MyISAM 总是一次性获得全部的锁,这样的话要么全部满足可以执行,要么就需要全部等待。

MVCC

MVCC 的英文全称是 Multiversion Concurrency Control,中文翻译过来就是多版本并发控制技术。从名字中也能看出来,MVCC 是通过数据行的多个版本管理来实现数据库的并发控制,简单来说它的思想就是保存数据的历史版本。这样我们就可以通过比较版本号决定数据是否显示出来(具体的规则后面会介绍到),读取数据的时候不需要加锁也可以保证事务的隔离效果。

通过 MVCC 我们可以解决以下几个问题:

  1. 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
  2. 降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
  3. 解决一致性读的问题。一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。

MVCC 的核心就是 Undo Log+ Read View,“MV”就是通过 Undo Log 来保存数据的历史版本,实现多版本的管理,“CC”是通过 Read View 来实现管理,通过 Read View 原则来决定数据是否显示。同时针对不同的隔离级别,Read View 的生成策略不同,也就实现了不同的隔离级别。

MVCC 是一种机制,MySQL、Oracle、SQL Server 和 PostgreSQL 的实现方式均有不同,我们在学习的时候,更主要的是要理解 MVCC 的设计思想。

mysql插入前先检查是否存在 mysql先查询后修改_mysql插入前先检查是否存在_06

查询优化器的两种优化方式

查询优化器的目的就是生成最佳的执行计划,而生成最佳执行计划的策略通常有以下两种方式。

第一种是基于规则的优化器(RBO,Rule-Based Optimizer),规则就是人们以往的经验,或者是采用已经被证明是有效的方式。通过在优化器里面嵌入规则,来判断 SQL 查询符合哪种规则,就按照相应的规则来制定执行计划,同时采用启发式规则去掉明显不好的存取路径。

第二种是基于代价的优化器(CBO,Cost-Based Optimizer),这里会根据代价评估模型,计算每条可能的执行计划的代价,也就是 COST,从中选择代价最小的作为执行计划。相比于 RBO 来说,CBO 对数据更敏感,因为它会利用数据表中的统计信息来做判断,针对不同的数据表,查询得到的执行计划可能是不同的,因此制定出来的执行计划也更符合数据表的实际情况。

RBO 的方式更像是一个出租车老司机,凭借自己的经验来选择从 A 到 B 的路径。而 CBO 更像是手机导航,通过数据驱动,来选择最佳的执行路径。

CBO 的代价计算是个复杂的过程,细节很多,不同优化器的实现方式也不同。另外随着优化器的逐渐成熟,考虑的因素也会越来越多。在某些情况下 MySQL 还会把 RBO 和 CBO 组合起来一起使用。RBO 是个简单固化的模型,在 Oracle 8i 之前采用的就是 RBO,在优化器中一共包括了 15 种规则,输入的 SQL 会根据符合规则的情况得出相应的执行计划,在 Oracle 10g 版本之后就用 CBO 替代了 RBO。

CBO 中需要传入的参数除了 SQL 查询以外,还包括了优化器参数、数据表统计信息和系统配置等,这实际上也导致 CBO 出现了一些缺陷,比如统计信息不准确,参数配置过高或过低,都会导致路径选择的偏差。除此以外,查询优化器还需要在优化时间和执行计划质量之间进行平衡,比如一个执行计划的执行时间是 10 秒钟,就没有必要花 1 分钟优化执行计划,除非该 SQL 使用频繁高,后续可以重复使用该执行计划。同样 CBO 也会做一些搜索空间的剪枝,以便在有效的时间内找到一个“最优”的执行计划。这里,其实也是在告诉我们,为了得到一个事物,付出的成本过大,即使最终得到了,有时候也是得不偿失的。

mysql插入前先检查是否存在 mysql先查询后修改_mysql插入前先检查是否存在_07

数据库服务器的优化步骤

mysql插入前先检查是否存在 mysql先查询后修改_先查询后修改并发的时候sql_08

mysql插入前先检查是否存在 mysql先查询后修改_先查询后修改并发的时候sql_09