MySQl的视图特性

  MySQL5.0版本之后开始引入视图。视图本身是一个虚拟表,不存放任何数据。在使用SQL语句访问视图的时候,它返回的数据是MySQL从其他表中生成的。视图和表是在同一个命名空间,MySQL在很多地方对于视图和表是同样对待的。不过视图和表也有不同,例如,不能对视图创建触发器,也不能使用DROP TABLE命令删除视图。

  在MySQL官方手册中对如何创建和使用视图有详细的介绍,这里不会详细介绍。我们将主要介绍视图是如何实现的,以及优化器如何处理视图,通过了解这些,希望可以让大家在使用视图时获得更高的性能。将使用示例数据库world来演示视图是如何工作的:

mysql> CREATE VIEW Oceania AS
    ->    SELECT * FROM Country WHERE Continent = 'Oceania'
    ->    WITH CHECK OPTION;

  实现视图最简单的方法是将SELECT语句的结果存放到临时表中。当需要访问视图的时候,直接访问这个临时表就可以了。我们先来看看下面的査询:

mysql> SELECT Code, Name FROM Oceania WHERE Name = 'Australia';

  下面是使用临时表来模拟视图的方法。这里临时表的名字是为演示用的:

mysql> CREATE TEMPORARY TABLE TMP_Oceania_123 AS
    ->    SELECT * FROM Country WHERE Continent = 'Oceania';
mysql> SELECT Code, Name FROM TMP_Oceania_123 WHERE Name = 'Australia';

  这样做会有明显的性能问题,优化器也很难优化在这个临时表上的査询。实现视图更好的方法是,重写含有视图的査询,将视图的定义SQL直接包含进査询的SQL中。下面的例子展示的是将视图定义的SQL合并进査询SQL后的样子:

mysql> SELECT Code, Name FROM Country
    -> WHERE Continent = 'Oceania' AND Name = 'Australia';

  MySQL可以使用这两种办法中的任何一种来处理视图。这两种算法分别称为合并算法 (MERGE)和临时表算法(TEMPTABLE),如果可能,会尽可能地使用合并算法。MySQL甚至可以嵌套地定义视图,也就是在一个视图上再定义另一个视图。可以在EXPLAIN EXTENDED之后使用SHOW WARNINGS来査看使用视图的査询重写后的结果。

  如果是采用临时表算法实现的视图,EXPLAIN中会显示为派生表(DERIVED)。图7-1展示了这两种实现的细节。

  如果视图中包含GROUY BY、DISTINCT、任何聚合函数、UNION、子査询等,只要无法在原表记录和视图记录中建立一一映射的场景中,MySQL都将使用临时表算法来实现视图。上面列举的可能不全,而且这些规则在未来的版本中也可能会改变。如果你想确定 MySQL到底是使用合并算法还是临时表算法,可以EXPLAIN—条针对视图的简单査询:

mysql> EXPLAIN SELECT * FROM <view_name>;
+----+-------------+
| id | select_type |
+----+-------------+
|  1 | PRIMARY     |
|  2 | DERIVED     |
+----+-------------+

  这里的select_type为“DERIVED”,说明该视图是采用临时表算法实现的。不过要注意:如果产生的底层派生表很大,那么执行EXPLAIN可能会非常慢。因为在MySQL5.5和更老的版本中,EXPLAIN是需要实际执行并产生该派生表的。

  视图的实现算法是视图本身的属性,和作用在视图上的査询语句无关。例如,可以为一个基于简单査询的视图指定使用临时表算法:

CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM sakila.actor;

  实现该视图的SQL本身并不需要临时表,但基于该视图无论执行什么样的査询,视图都会生成一个临时表。

 

1.可更新视图

  可更新视图(updatable view)是指可以通过更新这个视图来更新视图涉及的相关表。只要指定了合适的条件,就可以更新、删除甚至向视图中写入数据。例如,下面就是一个合理的操作:

mysql> UPDATE Oceania SET Population = Population * 1.1 WHERE Name = 'Australia';

  如果视图定义中包含了GROUP BY、UNION、聚合函数,以及其他一些特殊情况,就不能被更新了。更新视图的査询也可以是一个关联语句,但是有一个限制,被更新的列必须来自同一个表中。另外,所有使用临时表算法实现的视图都无法被更新。

  在上一节定义视图时使用的CHECK  OPTION子句,表示任何通过视图更新的行,都必须符合视图本身的WHERE条件定义。所以不能更新视图定义列以外的列,比如上例中不能更新Continent列,也不能插入不同Continent值的新数据,否则MySQL会报如下的错误:

mysql> UPDATE Oceania SET Continent = 'Atlantis';
ERROR 1369 (HY000): CHECK OPTION failed 'world.Oceania'

  某些关系数据库允许在视图上建立INSTEAD OF触发器,通过触发器可以精确控制在修改视图数据时做些什么。不过MySQL不支持在视图上建任何触发器。

 

2.视图对性能的影响

  多数人认为视图不能提升性能,实际上,在MySQL中某些情况下视图也可以帮助提升性能。而且视图还可以和其他提升性能的方式叠加使用。例如,在重构schema的时候可以使用视图,使得在修改视图底层表结构的时候,应用代码还可能继续不报错的运行。

  可以使用视图实现基于列的权限控制,却不需要真正的在系统中创建列权限,因此没有额外的开销。

CREATE VIEW public.employeeinfo AS
   SELECT firstname, lastname -- but not socialsecuritynumber
   FROM private.employeeinfo;
GRANT SELECT ON public.* TO public_user;

  有时候也可以使用伪临时视图实现一些功能。MySQL虽然不能创建只在当前连接中存在的真正的临时视图,但是可以建一个特殊名字的视图,然后在连接结束的时候删除该视图。这样在连接过程中就可以在FROM子句中使用这个视图,和使用子査询的方式完全相同,因为MySQL在处理视图和处理子査询的代码路径完全不同,所以它们的性能也不同。下面是一个例子:

-- Assuming 1234 is the result of CONNECTION_ID()
CREATE VIEW temp.cost_per_day_1234 AS
   SELECT DATE(ts) AS day, sum(cost) AS cost
   FROM logs.cost
   GROUP BY day;
SELECT c.day, c.cost, s.sales
FROM temp.cost_per_day_1234 AS c
   INNER JOIN sales.sales_per_day AS s USING(day);
DROP VIEW temp.cost_per_day_1234;

  我们这里使用连接ID作为视图名字的一部分来避免冲突。在应用发生崩溃和别的意外导致未清理临时视图的时候,这个技巧使得清理临时视图变得很简单。详细的信息可以参考后面的“丢失的临时表”。

  使用临时表算法实现的视图,在某些时候性能会很糟糕(虽然可能比直接使用等效査询语句要好一点)。MySQL以递归的方式执行这类视图,先会执行外层查询,即使外层査询优化器将其优化得很好,但是MySQL优化器可能无法像其他的数据库那样做更多的内外结合的优化。外层查询的WHERE条件无法“下推”到构建视图的临时表的査询中,临时表也无法建立索引(后面的版本可能会增加,可以关注官网)。下面是一个例子,还是基于temp.cost_per_day_1234这个视图:

mysql> SELECT c.day, c.cost, s.sales
    -> FROM temp.cost_per_day_1234 AS c
    ->    INNER JOIN sales.sales_per_day AS s USING(day)
    ->    WHERE day BETWEEN '2007-01-01' AND '2007-01-31';

  在这个查询中,MySQL先执行视图的SQL生成临时表,然后再将sales_per_day和临时表进行关联。这里的WHERE子句中的BETWEEN条件并不能下推到视图当中,所以视图在创建的时候仍然需要将所有的数据都放到临时表当中,而不仅仅是一个月的数据。而且临时表中不会有索引。这个案例中,索引还不是问题:MySQL将临时表作为关联顺序中的第一个表,因此这里可以使用sales_per_day中的索引。不过,如果是对两个视图做关联的话,优化器就没有任何索引可以使用了。

  视图还引入了一些并非MySQL特有的其他问题。很多开发者以为视图很简单,但实际上其背后的逡辑可能非常复杂。开发人员如果没有意识到视图背后的复杂性,很可能会以为是在不停地重复査询一张简单的表,而没有意识到实际上是代价高昂的视图。我们见过不少案例,一条看起来很简单的査询,EXPLAIN出来却有几百行,因为其中一个或者多个表,实际上是引用了很多其他表的视图。

  如果打算使用视图来提升性能,需要做比较详细的测试。即使是合并算法实现的视图也 会有额外的开销,而且视图的性能很难预测。在MySQL优化器中,视图的代码执行路径也完全不同,这部分代码测试还不够全面,可能会有一些隐藏缺陷和问题。所以,我们认为视图还不是那么成熟。例如,我们看到过这样的案例,复杂的视图和高并发的查询导致查询优化器花了大量时间在执行计划生成和统计数据阶段,这甚至会导致MySQL服务器僵死,后来通过将视图转换成等价的査询语句解决了问题。这也说明视图——即使是使用合并算法实现的——并不总是有很优化的实现。

 

3.视图的限制

  在其他的关系数据库中你可能使用过物化视图,MySQL还不支持物化视图(物化视图是指将视图结果数据存放在一个可以査看的表中,并定期从原始表中刷新数据到这个表中)(以后的版本可能增加,请关注官网)。MySQL也不支持在视图中创建索引。不过,可以使用构建缓存表或者汇总表的办法来模拟物化视图和索引。可以直接使用Justin Swanhart’s的工具Flexviews来实现这个目的。

  MySQL视图实现上也有一些让人烦恼的地方。例如,MySQL并不会保存视图定义的原始SQL语句,所以如果打算通过执行SHOW CREATE VIEW后再简单地修改其结果的方式来重新定义视图,可能会大失所望。SHOW CREATE VIEW出来的视图创建语句将以一种不友好的内部格式呈现,充满了各种转义符和引号,没有代码格式化,没有注释,也没有缩进。

  如果打算重新修改一个视图,并且没法找到视图的原始的创建语句的话,可以通过使用视图的.frm文件的最后一行获得一些信息。如果有FILE权限,甚至可以直接使用SQL语句中的LOAD_FILE()来读取.frm中的视图创建信息。再加上一些字符处理工作,就可以获得一个完整的视图创建语句了,感谢Roland Bouman创造性的实现:

mysql> SELECT
    ->    REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
    ->    REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
    ->       SUBSTRING_INDEX(LOAD_FILE('/var/lib/mysql/world/Oceania.frm'),
    ->       '\nsource=', −1),
    ->    '\\_','\_'), '\\%','\%'), '\\\\','\\'), '\\Z','\Z'), '\\t','\t'),
    ->    '\\r','\r'), '\\n','\n'), '\\b','\b'), '\\\"','\"'), '\\\'','\''),
    ->    '\\0','\0')
    -> AS source;
+-------------------------------------------------------------------------+
| source                                                                  |
+-------------------------------------------------------------------------+
| SELECT * FROM Country WHERE continent = 'Oceania'
   WITH CHECK OPTION
|
+-------------------------------------------------------------------------+

 

4.总结

  对好几个表的复杂査询,使用视图有时候会大大简化问题。当视图使用临时表时,无法将WHERE条件下推到各个具体的表,也不能使用任何索引,需要特别注意这类査询的性能。如果为了便利,使用视图是很合适的。

  外键限制会将约束放到MySQL中,这对于必须维护外键的场景,性能会更高。不过这也会带来额外的复杂性和额外的索引消耗,还会增加多表之间的交互,会导致系统中更多的锁和竞争。外键可以被看作是一个确保系统完整性的额外的特性,但是如果设计的是一个高性能的系统,那么外键就显得很臃肿了。很多人在更在意系统的性能的时候都不会使用外键,而是通过应用程序来维护。

posted @ 2021-11-13 16:51  小家电维修  阅读(323)  评论(0编辑  收藏  举报