MySQL之三:索引及执行计划

一.扩展php

https://www.yiibai.com/mysql/stored-function.htmlhtml

1.子查询

  • 定义python

 子查询容许把一个查询嵌套在另外一个查询当中。
 子查询,又叫内部查询,相对于内部查询,包含内部查询的就称为外部查询。
 子查询能够包含普通select能够包括的任何子句,好比:distinct、 group by、order by、limit、join和union等;可是对应的外部查询必须是如下语句之一:select、insert、update、delete、set或 者do。
 子查询的位置:
 select 中、from 后、where 中.group by 和order by 中无实用意义

分类:mysql

可使用的操做符:= > < >= <= <> ANY IN SOME ALL EXISTS 一个子查询会返回一个标量(就一个值)、一个行、一个列或一个表,这些子查询称之为标量、行、列和表子查询。 若是子查询返回一个标量值(就一个值),那么外部查询就可使用:=、>、<、>=、<=和<>符号进行比较判断;若是子查询返回的不是一个标量值,而外部查询使用了比较符和子查询的结果进行了比较,那么就会抛出异常。linux

1.1标量子查询

定义git

子查询返回的是单一值的标量,如一个数字或一个字符串,也是子查询中最简单的返回形式。 可使用 = > < >= <= <> 这些操做符对子查询的标量结果进行比较,一般子查询的位置在比较式的右侧 github

例子:

 SELECT * FROM article WHERE uid = (SELECT uid FROM user WHERE status=1 ORDER BY uid DESC LIMIT 1)
 SELECT * FROM t1 WHERE column1 = (SELECT MAX(column2) FROM t2)
 SELECT * FROM article AS t WHERE 2 = (SELECT COUNT(*) FROM article WHERE article.uid = t.uid)

1.2 列子查询

定义面试

指子查询返回的结果集是 N 行一列,该结果一般来自对表的某个字段查询返回。 可使用 = > < >= <= <> 这些操做符对子查询的标量结果进行比较,一般子查询的位置在比较式的右侧 ;可使用 IN、ANY、SOME 和 ALL 操做符,不能直接使用 = > < >= <= <> 这些比较标量结果的操做符。 算法

 SELECT * FROM article WHERE uid IN(SELECT uid FROM user WHERE status=1)
 SELECT s1 FROM table1 WHERE s1 > ANY (SELECT s2 FROM table2)
 SELECT s1 FROM table1 WHERE s1 > ALL (SELECT s2 FROM table2) 

注意

NOT IN 是 <> ALL 的别名,两者相同。 若是 table2 为空表,则 ALL 后的结果为 TRUE; 若是子查询返回如 (0,NULL,1) 这种尽管 s1 比返回结果都大,但有空行的结果,则 ALL 后的结果为 UNKNOWN 。 对于 table2 空表的状况,下面的语句均返回 NULL: sql

 SELECT s1 FROM table1 WHERE s1 > (SELECT s2 FROM table2)
 SELECT s1 FROM table1 WHERE s1 > ALL (SELECT MAX(s1) FROM table2)

1.3 行子查询

定义:

指子查询返回的结果集是一行 N 列,该子查询的结果一般是对表的某行数据进行查询而返回的结果集。

例子:

 SELECT * FROM table1 WHERE (1,2) = (SELECT column1, column2 FROM table2)
 注:(1,2) 等同于 row(1,2)
 SELECT * FROM article WHERE (title,content,uid) = (SELECT title,content,uid FROM blog WHERE bid=2)

1.4 表子查询

指子查询返回的结果集是 N 行 N 列的一个表数据。

例子:

SELECT * FROM article WHERE (title,content,uid) IN (SELECT title,content,uid FROM blog)

不能优化的子查询

http://www.cnblogs.com/loveyouyou616/archive/2012/12/21/2827655.html

 1.mysql不支持子查询合并和聚合函数子查询优化,mariadb对聚合函数子查询进行物化优化;
   2.mysql不支持from子句子查询优化,mariadb对from子句子查询进行子查询上拉优化;
   3.mysql和mariadb对子查询展开提供有限的支持,如对主键的操做才能进行上拉子查询优化;
   4.mysql不支持exists子查询优化,mariadb对exists关联子查询进行半链接优化,对exists非关联子查询没有进一步进行优化;
   5.mysql和mariadb不支持not exists子查询优化;
   6.mysql和mariadb对in子查询,对知足半链接语义的查询进行半链接优化,再基于代价评估进行优化,二者对半链接的代价评估选择方式有差别;
   7.mysql不支持not in子查询优化,mariadb对非关联not in子查询使用物化优化,对关联not in子查询不作优化;
   8.mysql和mariadb对>all非关联子查询使用max函数,<all非关联子查询使用min函数,对=all和非关联子查询使用exists优化;
   9.对>some和>any非关联子查询使用min函数,对<some和<any非关联子查询使用max函数,=any 和=some子查询使用半链接进行优化,对>some和>any关联子查询以及<some和<any关联子查询只有exists 优化。

2.内置函数

2.1 数学函数

 abs(x)
 pi()
 mod(x,y)
 sqrt(x)
 ceil(x)或者ceiling(x)
 rand(),rand(N):返回0-1间的浮点数,使用不一样的seed N能够得到不一样的随机数
 round(x, D):四舍五入保留D位小数,D默认为0, 能够为负数, 如round(19, -1)返回20
 truncate(x, D):截断至保留D位小数,D能够为负数, 如trancate(19,-1)返回10
 sign(x): 返回x的符号,正负零分别返回1, -1, 0
 pow(x,y)或者power(x,y)
 exp(x):e^x
 log(x):天然对数
 log10(x):以10为底的对数
 radians(x):角度换弧度
 degrees(x):弧度换角度
 sin(x)和asin(x):
 cos(x)和acos(x):
 tan(x)和atan(x):
 cot(x):

2.2 字符串函数

 
char_length(str):返回str所包含的字符数,一个多字节字符算一个字符
 length(str): 返回字符串的字节长度,如utf8中,一个汉字3字节,数字和字母算一个字节
 concat(s1, s1, ...): 返回链接参数产生的字符串
 concat_ws(x, s1, s2, ...): 使用链接符x链接其余参数产生的字符串
 INSERT(str,pos,len,newstr):返回str,其起始于pos,长度为len的子串被newstr取代。
 1. 若pos不在str范围内,则返回原字符串str
 2. 若str中从pos开始的子串不足len,则将从pos开始的剩余字符用newstr取代
 3. 计算pos时从1开始,若pos=3,则从第3个字符开始替换
 lower(str)或者lcase(str):
 upper(str)或者ucase(str):
 left(s,n):返回字符串s最左边n个字符
 right(s,n): 返回字符串最右边n个字符
 lpad(s1, len, s2): 用s2在s1左边填充至长度为len, 若s1的长度大于len,则截断字符串s1至长度len返回
 rpad(s1, len, s2):
 ltrim(s):删除s左侧空格字符
 rtrim(s):
 TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM] str)或TRIM([remstr FROM] str):从str中删除remstr, remstr默认为空白字符
 REPEAT(str,count):返回str重复count次获得的新字符串
 REPLACE(str,from_str,to_str): 将str中的from_str所有替换成to_str
 SPACE(N):返回长度为N的空白字符串
 STRCMP(str1,str2):若str1和str2相同,返回0, 若str1小于str2, 返回-1, 不然返回1.
 SUBSTRING(str,pos), SUBSTRING(str FROM pos), SUBSTRING(str,pos,len), SUBSTRING(str FROM pos FOR len),MID(str,pos,len): 获取特定位置,特定长度的子字符串
 LOCATE(substr,str), LOCATE(substr,str,pos),INSTR(str,substr),POSITION(substr IN str): 返回字符串中特定子串的位置,注意这里INSTR与其余函数的参数位置是相反的
 REVERSE(str)
 ELT(N,str1,str2,str3,...):返回参数strN, 若N大于str参数个数,则返回NULL
 FIELD(str,str1,str2,str3,...): 返回str在后面的str列表中第一次出现的位置,若找不到str或者str为NULL, 则返回0
 FIND_IN_SET(str,strlist):strlist是由','分隔的字符串,若str不在strlist或者strlist为空字符串,则返回0;若任意一个参数为NULL则返回NULL
 MAKE_SET(bits,str1,str2,...): 由bits的做为位图来选取strN参数,选中的参数用','链接后返回

2.3日期和时间函数

 
CURDATE(), CURRENT_DATE, CURRENT_DATE():用于获取当前日期,格式为'YYYY-MM-DD'; 若+0则返回YYYYMMDD
 UTC_DATE, UTC_DATE():返回当前世界标准时间
 CURTIME([fsp]), CURRENT_TIME, CURRENT_TIME([fsp]): 用于获取当前时间, 格式为'HH:MM:SS' 若+0则返回 HHMMSS
 UTC_TIME, UTC_TIME([fsp])
 CURRENT_TIMESTAMP, CURRENT_TIMESTAMP([fsp]), LOCALTIME, LOCALTIME([fsp]), SYSDATE([fsp]), NOW([fsp]): 用于获取当前的时间日期,格式为'YYYY-MM-DD HH:MM:SS',若+0则返回YYYYMMDDHHMMSS
 UTC_TIMESTAMP, UTC_TIMESTAMP([fsp])
 UNIX_TIMESTAMP(), UNIX_TIMESTAMP(date):返回一个unix时间戳('1970-01-01 00:00:00' UTC至今或者date的秒数),这其实是从字符串到整数的一个转化过程
 FROM_UNIXTIME(unix_timestamp), FROM_UNIXTIME(unix_timestamp,format):从时间戳返回'YYYY-MM-DD HH:MM:SS' 或者YYYYMMDDHHMMSS,加入format后根据所需的format显示。
 MONTH(date)
 MONTHNAME(date)
 DAYNAME(date)
 DAY(date),DAYOFMONTH(date):1-31或者0
 DAYOFWEEK(date):1-7==>星期天-星期六
 DAYOFYEAR(date): 1-365(366)
 WEEK(date[,mode]):判断是一年的第几周,若是1-1所在周在新的一年多于4天,则将其定为第一周;不然将其定为上一年的最后一周。mode是用来人为定义一周从星期几开始。
 WEEKOFYEAR(date):相似week(date,3),从周一开始计算一周。
 QUARTER(date):返回1-4
 HOUR(time):返回时间中的小时数,能够大于24
 MINUTE(time):
 SECOND(time):
 EXTRACT(unit FROM date):提取日期时间中的要素
 TIME_TO_SEC(time)
 SEC_TO_TIME(seconds)
 TO_DAYS(date): 从第0年开始的天数
 TO_SECNDS(expr):从第0年开始的秒数
 ADDDATE(date,INTERVAL expr unit), ADDDATE(expr,days),DATE_ADD(date,INTERVAL expr unit)
 DATE_SUB(date,INTERVAL expr unit), DATE_SUB(date,INTERVAL expr unit)
 ADDTIME(expr1,expr2)
 SUBTIME(expr1,expr2)
 DATE_FORMAT(date,format):
 DATEDIFF(expr1,expr2):返回相差的天数
 TIMEDIFF(expr1,expr2):返回相隔的时间

注意:时间日期的加减也能够直接用+/-来进行

 date + INTERVAL expr unit
 date - INTERVAL expr unit
 如:
 SELECT '2008-12-31 23:59:59' + INTERVAL 1 SECOND;##'2009-01-01 00:00:00'
 SELECT INTERVAL 1 DAY + '2008-12-31';##'2009-01-01'
 SELECT '2005-01-01' - INTERVAL 1 SECOND;##'2004-12-31 23:59:59'

2.4条件判断函数

 IF(expr1,expr2,expr3):若是expr1不为0或者NULL,则返回expr2的值,不然返回expr3的值
 IFNULL(expr1,expr2):若是expr1不为NULL,返回expr1,不然返回expr2
 NULLIF(expr1,expr2): 若是expr1=expr2则返回NULL, 不然返回expr2
 CASE value WHEN [compare_value] THEN result [WHEN [compare_value] THEN result ...] [ELSE result] END
 当compare_value=value时返回result
 CASE WHEN [condition] THEN result [WHEN [condition] THEN result ...] [ELSE result] END
 当condition为TRUE时返回result
 SELECT CASE 1 WHEN 1 THEN 'one'
 WHEN 2 THEN 'two' ELSE 'more' END;##'one'
 SELECT CASE WHEN 1>0 THEN 'true' ELSE 'false' END;##'true'
 SELECT CASE BINARY 'B'
 WHEN 'a' THEN 1 WHEN 'b' THEN 2 END;##NULL

2.5系统信息函数

 VERSION():返回mysql服务器的版本,是utf8编码的字符串
 CONNECTION_ID():显示链接号(链接的线程号)
 DATABASE(),SCHEMA():显示当前使用的数据库
 SESSION_USER(), SYSTEM_USER(), USER(), CURRENT_USER, CURRENT_USER():返回当前的用户名@主机,utf8编码字符串
 CHARSET(str)
 COLLATION(str)
 LAST_INSERT_ID():自动返回最后一个insert或者update查询, 为auto_increment列设置的第一个发生的值

2.6加密和压缩函数

PASSWORD(str):这个函数的输出与变量old_password有关。old_password 在mysql5.6中默认为0。 不一样取值的效果以下表

 old_password=1时, password(str)的效果与old_password(str)相同,因为其不够安全已经弃用(5.6.5之后)。
old_password=2时,在生成哈希密码时会随机加盐。
MD5(str):计算MD5 128位校验和,返回32位16进制数构成的字符串,当str为NULL时返回NULL。能够用做哈希密码
 SHA1(str), SHA(str):计算160位校验和,返回40位16进制数构成的字符串,当str为NULL时返回NULL。
 SHA2(str, hash_length):计算SHA-2系列的哈希方法(SHA-224, SHA-256, SHA-384, and SHA-512). 第一个参数为待校验字符串,第二个参数为结果的位数(224, 256, 384, 512)
 ENCRYPT(str[,salt]): 用unix crypt()来加密str. salt至少要有两位字符,不然会返回NULL。若未指定salt参数,则会随机添加salt。
ECODE(crypt_str,pass_str):解密crypt_str, pass_str用做密码
ENCODE(str,pass_str):用pass_str做为密码加密str
DES_ENCRYPT(str[,{key_num|key_str}]):用Triple-DES算法编码str, 这个函数只有在mysql配置成支持ssl时才可用。
DES_DECRYPT(crypt_str[,key_str])
AES_ENCRYPT(str,key_str[,init_vector])
AES_DECRYPT(crypt_str,key_str[,init_vector])
COMPRESS(string_to_compress):返回二进制码
UNCOMPRESS(string_to_uncompress)

2.7 聚合函数

若在没使用group by时使用聚合函数,至关于把全部的行都归于一组来进行处理。除非特殊说明,通常聚合函数会忽略掉NULL. AVG([DISTINCT] expr): 返回expr的平均值,distinct选项用于忽略重复值 COUNT([DISTINCT] expr):返回select中expr的非0值个数,返回值为bigint类型 group_concat:链接组内的非空值,若无非空值,则返回NULL

 GROUP_CONCAT([DISTINCT] expr [,expr ...]
 [ORDER BY {unsigned_integer | col_name | expr}
 [ASC | DESC] [,col_name ...]]
 [SEPARATOR str_val])
 MAX([DISTINCT] expr)
 MIN([DISTINCT] expr)
 SUM([DISTINCT] expr)
 VAR_POP(expr)
 VARIANCE(expr):同VAR_POP(expr),可是这是标准sql的一个扩展函数
 VAR_SAMP(expr)
 STD(expr): 这是标准sql的一个扩展函数
 STDDEV(expr):这个函数是为了跟oracle兼容而设置的
 STDDEV_POP(expr):这个是sql标准函数
 STDDEV_SAMP(expr):样本标准差

2.8格式或类型转化函数

 FORMAT(X,D[,locale]):将数字X转化成'#,###,###.##'格式,D为保留的小数位数
 CONV(N,from_base,to_base):改变数字N的进制,返回值为该进制下的数字构成的字符串
 INET_ATON(expr):ip字符串转数字
 INET_NTOA(expr):数字转ip字符串
 CAST(expr AS type):转换数据类型
 CONVERT(expr,type), CONVERT(expr USING transcoding_name): type能够为BINARY[(N)],CHAR[(N)],DATE,DATETIME, DECIMAL[(M[,D])],DECIMAL[(M[,D])],TIME,UNSIGNED [INTEGER]等等。transcoding_name如utf8等等

3.存储过程

存储过程是存储在数据库目录中的一段声明性SQL语句。 触发器,其余存储过程以及JavaPythonPHP等应用程序能够调用存储过程。

自身的存储过程称为递归存储过程。大多数数据库管理系统支持递归存储过程。 可是,MySQL不支持它。

 

 

MySQL中存储过程

MySQL是最受欢迎的开源RDBMS,被社区和企业普遍使用。 然而,在它发布的第一个十年期间,它不支持存储过程,存储函数触发器事件。自从MySQL 5.0版本以来,这些功能被添加到MySQL数据库引擎,使其更加灵活和强大。

MySQL存储过程的优势

一般存储过程有助于提升应用程序的性能。当建立,存储过程被编译以后,就存储在数据库中。 可是,MySQL实现的存储过程略有不一样。 MySQL存储过程按需编译。 在编译存储过程以后,MySQL将其放入缓存中。 MySQL为每一个链接维护本身的存储过程高速缓存。 若是应用程序在单个链接中屡次使用存储过程,则使用编译版本,不然存储过程的工做方式相似于查询。

存储过程有助于减小应用程序和数据库服务器之间的流量,由于应用程序没必要发送多个冗长的SQL语句,而只能发送存储过程的名称和参数。

存储的程序对任何应用程序都是可重用的和透明的。 存储过程将数据库接口暴露给全部应用程序,以便开发人员没必要开发存储过程当中已支持的功能。

存储的程序是安全的。 数据库管理员能够向访问数据库中存储过程的应用程序授予适当的权限,而不向基础数据库表提供任何权限。

除了这些优势以外,存储过程有其自身的缺点,在数据库中使用它们以前,您应该注意这些缺点。

MySQL存储过程的缺点

若是使用大量存储过程,那么使用这些存储过程的每一个链接的内存使用量将会大大增长。 此外,若是您在存储过程当中过分使用大量逻辑操做,则CPU使用率也会增长,由于数据库服务器的设计不当于逻辑运算。

存储过程的构造使得开发具备复杂业务逻辑的存储过程变得更加困难。

很难调试存储过程。只有少数数据库管理系统容许您调试存储过程。不幸的是,MySQL不提供调试存储过程的功能。

开发和维护存储过程并不容易。开发和维护存储过程一般须要一个不是全部应用程序开发人员拥有的专业技能。这可能会致使应用程序开发和维护阶段的问题。

MySQL存储过程有本身的优势和缺点。开发应用程序时,您应该决定是否应该或不该该根据业务需求使用存储过程。

函数

MySQL聚合函数

MySQL字符串函数

MySQL控制流函数

  • case()函数 - 若是知足WHEN分支中的条件,则返回THEN分支中的相应结果,不然返回ELSE分支中的结果。

  • if语句 - 根据给定的条件返回一个值。

  • ifnull()函数 - 若是第一个参数不为NULL,则返回第一个参数,不然返回第二个参数。

  • nullif()函数 - 若是第一个参数等于第二个参数,则返回NULL,不然返回第一个参数。

MySQL日期和时间函数

MySQL比较函数

其余MySQL函数

触发器

SQL触发器是存储在数据库目录中的一组SQL语句。每当与表相关联的事件发生时,即会执行或触发SQL触发器,例如插入,更新或删除。

SQL触发器是一种特殊类型的存储过程。 这是特别的,由于它不像直接像存储过程那样调用。 触发器和存储过程之间的主要区别在于,当对表执行数据修改事件时,会自动调用触发器,而存储过程必需要明确地调用。

了解SQL触发器的优缺点很是重要,以便您能够适当地使用它。在如下部分中,咱们将讨论使用SQL触发器的优缺点。

SQL触发器的优势

  • SQL触发器提供了检查数据完整性的替代方法。

  • SQL触发器能够捕获数据库层中业务逻辑中的错误。

  • SQL触发器提供了运行计划任务的另外一种方法。经过使用SQL触发器,您没必要等待运行计划的任务,由于在对表中的数据进行更改以前或以后自动调用触发器。

  • SQL触发器对于审核表中数据的更改很是有用。

SQL触发器的缺点

  • SQL触发器只能提供扩展验证,而且没法替换全部验证。一些简单的验证必须在应用层完成。 例如,您可使用JavaScript或服务器端使用服务器端脚本语言(如JSPPHP,ASP.NET,Perl等)来验证客户端的用户输入。

  • 从客户端应用程序调用和执行SQL触发器不可见,所以很难弄清数据库层中发生的状况。

  • SQL触发器可能会增长数据库服务器的开销。

MySQL触发器简介

在MySQL中,触发器是一组SQL语句,当对相关联的表上的数据进行更改时,会自动调用该语句。 触发器能够被定义为在INSERTUPDATEDELETE语句更改数据以前或以后调用。在MySQL5.7.2版本以前,每一个表最多能够定义六个触发器。

  • BEFORE INSERT - 在数据插入表以前被激活触发器。

  • AFTER INSERT - 在将数据插入表以后激活触发器。

  • BEFORE UPDATE - 在表中的数据更新以前激活触发器。

  • AFTER UPDATE - 在表中的数据更新以后激活触发器。

  • BEFORE DELETE - 在从表中删除数据以前激活触发器。

  • AFTER DELETE - 从表中删除数据以后激活触发器。

可是,从MySQL 5.7.2+版本开始,能够为相同的触发事件和动做时间定义多个触发器

当使用不使用INSERTDELETEUPDATE语句更改表中数据的语句时,不会调用与表关联的触发器。 例如,TRUNCATE语句删除表的全部数据,但不调用与该表相关联的触发器。

有些语句使用了后台的INSERT语句,如REPLACE语句LOAD DATA语句。若是使用这些语句,则调用与表关联的相应触发器。

必需要为与表相关联的每一个触发器使用惟一的名称。能够为不一样的表定义相同的触发器名称,这是一个很好的作法。

应该使用如下命名约定命名触发器:

 (BEFORE | AFTER)_tableName_(INSERT| UPDATE | DELETE)
 SQL

例如,before_order_update是更新orders表中的行数据以前调用的触发器。

如下命名约定与上述同样。

 tablename_(BEFORE | AFTER)_(INSERT| UPDATE | DELETE)
 SQL

例如,order_before_update与上述before_order_update触发器相同。

MySQL触发存储

MySQL在数据目录中存储触发器,例如:/data/yiibaidb/,并使用名为tablename.TRGtriggername.TRN的文件:

  • tablename.TRG文件将触发器映射到相应的表。

  • triggername.TRN文件包含触发器定义。

能够经过将触发器文件复制到备份文件夹来备份MySQL触发器。也能够使用mysqldump工具有份触发器

MySQL触发限制

MySQL触发器覆盖标准SQL中定义的全部功能。 可是,在应用程序中使用它们以前,您应该知道一些限制。

MySQL触发器不能:

  • 使用在SHOWLOAD DATALOAD TABLEBACKUP DATABASERESTOREFLUSHRETURN语句之上。

  • 使用隐式或明确提交或回滚的语句,如COMMITROLLBACKSTART TRANSACTIONLOCK/UNLOCK TABLESALTERCREATEDROPRENAME等。

  • 使用准备语句,如PREPAREEXECUTE

  • 使用动态SQL语句。

MySQL触发语法

为了建立一个新的触发器,可使用CREATE TRIGGER语句。 下面说明了CREATE TRIGGER语句的语法:

 CREATE TRIGGER trigger_name trigger_time trigger_event
  ON table_name
  FOR EACH ROW
  BEGIN
  ...
  END;
 SQL
  • 将触发器名称放在CREATE TRIGGER语句以后。触发器名称应遵循命名约定[trigger time]_[table name]_[trigger event],例如before_employees_update。

  • 触发激活时间能够在以前或以后。必须指定定义触发器的激活时间。若是要在更改以前处理操做,则使用BEFORE关键字,若是在更改后须要处理操做,则使用AFTER关键字。

  • 触发事件能够是INSERTUPDATEDELETE。此事件致使触发器被调用。 触发器只能由一个事件调用。要定义由多个事件调用的触发器,必须定义多个触发器,每一个事件一个触发器。

  • 触发器必须与特定表关联。没有表触发器将不存在,因此必须在ON关键字以后指定表名。

  • 将SQL语句放在BEGINEND块之间。这是定义触发器逻辑的位置。

MySQL触发器示例

下面咱们将在MySQL中建立触发器来记录employees表中行数据的更改状况。

 mysql> DESC employees;
 +----------------+--------------+------+-----+---------+-------+
 | Field         | Type         | Null | Key | Default | Extra |
 +----------------+--------------+------+-----+---------+-------+
 | employeeNumber | int(11)     | NO   | PRI | NULL   |       |
 | lastName       | varchar(50) | NO   |     | NULL   |       |
 | firstName     | varchar(50) | NO   |     | NULL   |       |
 | extension     | varchar(10) | NO   |     | NULL   |       |
 | email         | varchar(100) | NO   |     | NULL   |       |
 | officeCode     | varchar(10) | NO   | MUL | NULL   |       |
 | reportsTo     | int(11)     | YES | MUL | NULL   |       |
 | jobTitle       | varchar(50) | NO   |     | NULL   |       |
 +----------------+--------------+------+-----+---------+-------+
 8 rows in set
 SQL

首先,建立一个名为employees audit的新表,用来保存employees表中数据的更改。 如下语句建立employee_audit表。

 USE yiibaidb;
 CREATE TABLE employees_audit (
    id INT AUTO_INCREMENT PRIMARY KEY,
    employeeNumber INT NOT NULL,
    lastname VARCHAR(50) NOT NULL,
    changedat DATETIME DEFAULT NULL,
    action VARCHAR(50) DEFAULT NULL
 );
 SQL

接下来,建立一个BEFORE UPDATE触发器,该触发器在对employees表中的行记录更改以前被调用。

 DELIMITER $$
 CREATE TRIGGER before_employee_update
    BEFORE UPDATE ON employees
    FOR EACH ROW
 BEGIN
     INSERT INTO employees_audit
     SET action = 'update',
      employeeNumber = OLD.employeeNumber,
        lastname = OLD.lastname,
        changedat = NOW();
 END$$
 DELIMITER ;
 SQL

在触发器的主体中,使用OLD关键字来访问受触发器影响的行的employeeNumberlastname列。

请注意,在为INSERT定义的触发器中,能够仅使用NEW关键字。不能使用OLD关键字。可是,在为DELETE定义的触发器中,没有新行,所以您只能使用OLD关键字。在UPDATE触发器中,OLD是指更新前的行,而NEW是更新后的行。

而后,要查看当前数据库中的全部触发器,请使用SHOW TRIGGERS语句,以下所示:

 SHOW TRIGGERS;
 SQL

执行上面查询语句,获得如下结果 -

 mysql> SHOW TRIGGERS;
 +------------------------+--------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
 | Trigger               | Event | Table     | Statement                                                                                                                                                             | Timing | Created               | sql_mode                                                                         | Definer       | character_set_client | collation_connection | Database Collation |
 +------------------------+--------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
 | before_employee_update | UPDATE | employees | BEGIN
     INSERT INTO employees_audit
     SET action = 'update',
      employeeNumber = OLD.employeeNumber,
        lastname = OLD.lastname,
        changedat = NOW();
 END | BEFORE | 2017-08-02 22:06:36.40 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@localhost | utf8                 | utf8_general_ci     | utf8_general_ci   |
 +------------------------+--------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
 1 row in set
 SQL

以后,更新employees表以检查触发器是否被调用。

 UPDATE employees 
 SET
    lastName = 'Maxsu'
 WHERE
    employeeNumber = 1056;
 SQL

最后,要检查触发器是否被UPDATE语句调用,可使用如下查询来查询employees_audit表:

 SELECT * FROM employees_audit;
 SQL

如下是查询的输出:

 mysql> SELECT * FROM employees_audit;
 +----+----------------+----------+---------------------+--------+
 | id | employeeNumber | lastname | changedat           | action |
 +----+----------------+----------+---------------------+--------+
 |  1 |           1056 | Hill     | 2017-08-02 22:15:51 | update |
 +----+----------------+----------+---------------------+--------+
 1 row in set

MySQL将按照建立的顺序调用触发器。要更改触发器的顺序,须要在FOR EACH ROW子句以后指定FOLLOWSPRECEDES。以下说明 -

  • FOLLOWS选项容许新触发器在现有触发器以后激活。

  • PRECEDES选项容许新触发器在现有触发器以前激活。

如下是使用显式顺序建立新的附加触发器的语法:

 DELIMITER $$
 CREATE TRIGGER trigger_name
 [BEFORE|AFTER] [INSERT|UPDATE|DELETE] ON table_name
 FOR EACH ROW [FOLLOWS|PRECEDES] existing_trigger_name
 BEGIN
 
 END$$
 DELIMITER ;
 SQL

MySQL多重触发器示例

咱们来看如何一个在表中的同一个事件和动做上,建立多个触发器的例子。

下面将使用示例数据库(yiibaidb)中的products表进行演示。假设,每当更改产品的价格(MSRP列)时,要将旧的价格记录在一个名为price_logs的表中。

首先,使用CREATE TABLE语句建立一个新的price_logs表,以下所示:

 USE yiibaidb;
 CREATE TABLE price_logs (
   id INT(11) NOT NULL AUTO_INCREMENT,
   product_code VARCHAR(15) NOT NULL,
   price DOUBLE NOT NULL,
   updated_at TIMESTAMP NOT NULL DEFAULT 
              CURRENT_TIMESTAMP 
              ON UPDATE CURRENT_TIMESTAMP,
 ​
   PRIMARY KEY (id),
 ​
   KEY product_code (product_code),
 ​
   CONSTRAINT price_logs_ibfk_1 FOREIGN KEY (product_code) 
   REFERENCES products (productCode) 
   ON DELETE CASCADE 
   ON UPDATE CASCADE
 );
 SQL

其次,当表的BEFORE UPDATE事件发生时,建立一个新的触发器。触发器名称为before_products_update,具体实现以下所示:

 DELIMITER $$
 CREATE TRIGGER before_products_update 
    BEFORE UPDATE ON products 
    FOR EACH ROW 
 BEGIN
      INSERT INTO price_logs(product_code,price)
      VALUES(old.productCode,old.msrp);
 END$$
 DELIMITER ;
 SQL

第三,咱们更改产品的价格,并使用如下UPDATE语句,最后查询price_logs表:

 UPDATE products
 SET msrp = 95.1
 WHERE productCode = 'S10_1678';
 -- 查询结果价格记录
 SELECT * FROM price_logs;
 SQL

上面查询语句执行后,获得如下结果 -

 +----+--------------+-------+---------------------+
 | id | product_code | price | updated_at         |
 +----+--------------+-------+---------------------+
 |  1 | S10_1678     |  95.7 | 2017-08-03 02:46:42 |
 +----+--------------+-------+---------------------+
 1 row in set
 SQL

能够看到结果中,它按咱们预期那样工做了。

假设不只要看到旧的价格,改变的时候,还要记录是谁修改了它。 咱们能够向price_logs表添加其余列。 可是,为了实现多个触发器的演示,咱们将建立一个新表来存储进行更改的用户的数据。这个新表的名称为user_change_logs,结构以下:

 USE yiibaidb;
 CREATE TABLE user_change_logs (
   id int(11) NOT NULL AUTO_INCREMENT,
   product_code varchar(15) DEFAULT NULL,
   updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 
   ON UPDATE CURRENT_TIMESTAMP,
   updated_by varchar(30) NOT NULL,
   PRIMARY KEY (id),
   KEY product_code (product_code),
   CONSTRAINT user_change_logs_ibfk_1 FOREIGN KEY (product_code) 
   REFERENCES products (productCode) 
   ON DELETE CASCADE ON UPDATE CASCADE
 );
 SQL

如今,咱们建立一个在products表上的BEFORE UPDATE事件上激活的第二个触发器。 此触发器将更改的用户信息更新到user_change_logs表。 它在before_products_update触发后被激活。

 DELIMITER $$
 CREATE TRIGGER before_products_update_2 
    BEFORE UPDATE ON products 
    FOR EACH ROW FOLLOWS before_products_update
 BEGIN
    INSERT INTO user_change_logs(product_code,updated_by)
    VALUES(old.productCode,user());
 END$$
 DELIMITER ;
 SQL

下面咱们来作一个快速测试。

首先,使用UPDATE语句更新指定产品的价格,以下:

 UPDATE products
 SET msrp = 95.3
 WHERE productCode = 'S10_1678';
 SQL

其次,分别从price_logsuser_change_logs表查询数据:

 SELECT * FROM price_logs;
 SQL

上面查询语句执行后,获得如下结果 -

 mysql> SELECT * FROM price_logs;
 +----+--------------+-------+---------------------+
 | id | product_code | price | updated_at         |
 +----+--------------+-------+---------------------+
 |  1 | S10_1678     |  95.7 | 2017-08-03 02:46:42 |
 |  2 | S10_1678     |  95.1 | 2017-08-03 02:47:21 |
 +----+--------------+-------+---------------------+
 2 rows in set
 SQL
 SELECT * FROM user_change_logs;
 SQL

上面查询语句执行后,获得如下结果 -

 mysql> SELECT * FROM user_change_logs;
 +----+--------------+---------------------+----------------+
 | id | product_code | updated_at         | updated_by     |
 +----+--------------+---------------------+----------------+
 |  1 | S10_1678     | 2017-08-03 02:47:21 | root@localhost |
 +----+--------------+---------------------+----------------+
 1 row in set
 SQL

如上所见,两个触发器按照预期的顺序激活执行相关操做了。

触发器顺序

若是使用SHOW TRIGGERS语句,则不会在表中看到触发激活同一事件和操做的顺序。

 SHOW TRIGGERS FROM yiibaidb;
 SQL

要查找此信息,须要以下查询information_schema数据库的triggers表中的action_order列,以下查询语句 -

 SELECT 
     trigger_name, action_order
 FROM
     information_schema.triggers
 WHERE
     trigger_schema = 'yiibaidb'
 ORDER BY event_object_table , 
          action_timing , 
          event_manipulation;
 SQL

上面查询语句执行后,获得如下结果 -

 mysql> SELECT 
     trigger_name, action_order
 FROM
     information_schema.triggers
 WHERE
     trigger_schema = 'yiibaidb'
 ORDER BY event_object_table , 
          action_timing , 
          event_manipulation;
 +--------------------------+--------------+
 | trigger_name             | action_order |
 +--------------------------+--------------+
 | before_employee_update   |            1 |
 | before_products_update   |            1 |
 | before_products_update_2 |            2 |
 +--------------------------+--------------+
 3 rows in set

触发器做为纯文本文件存储在如下数据库文件夹中:

 /data_folder/database_name/table_name.trg
 SQL

也可经过查询information_schema数据库中的triggers表来显示触发器,以下所示:

 SELECT 
    *
 FROM
    information_schema.triggers
 WHERE
    trigger_schema = 'database_name'
         AND trigger_name = 'trigger_name';
 SQL

该语句容许您查看触发器的内容及其元数据,例如:关联表名和定义器,这是建立触发器的MySQL用户的名称。

若是要检索指定数据库中的全部触发器,则须要使用如下SELECT语句information_schema数据库中的triggers表查询数据:

 SELECT 
     *
 FROM
     information_schema.triggers
 WHERE
     trigger_schema = 'database_name';
 SQL
 

要查找与特定表相关联的全部触发器,请使用如下查询:

 SELECT 
    *
 FROM
    information_schema.triggers
 WHERE
    trigger_schema = 'database_name'
         AND event_object_table = 'table_name';
 SQL

例如,如下查询语句与yiibaidb数据库中的employees表相关联的全部触发器。

 SELECT * FROM information_schema.triggers
 WHERE trigger_schema = 'yiibaidb'
         AND event_object_table = 'employees';
 SQL

执行上面查询,获得如下结果 -

 mysql> SELECT * FROM information_schema.triggers
 WHERE trigger_schema = 'yiibaidb'
         AND event_object_table = 'employees';
 +-----------------+----------------+------------------------+--------------------+----------------------+---------------------+--------------------+--------------+------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+---------------+----------------------------+----------------------------+--------------------------+--------------------------+------------------------+-----------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
 | TRIGGER_CATALOG | TRIGGER_SCHEMA | TRIGGER_NAME           | EVENT_MANIPULATION | EVENT_OBJECT_CATALOG | EVENT_OBJECT_SCHEMA | EVENT_OBJECT_TABLE | ACTION_ORDER | ACTION_CONDITION | ACTION_STATEMENT                                                                                                                                                      | ACTION_ORIENTATION | ACTION_TIMING | ACTION_REFERENCE_OLD_TABLE | ACTION_REFERENCE_NEW_TABLE | ACTION_REFERENCE_OLD_ROW | ACTION_REFERENCE_NEW_ROW | CREATED                | SQL_MODE                                                                          | DEFINER        | CHARACTER_SET_CLIENT | COLLATION_CONNECTION | DATABASE_COLLATION |
 +-----------------+----------------+------------------------+--------------------+----------------------+---------------------+--------------------+--------------+------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+---------------+----------------------------+----------------------------+--------------------------+--------------------------+------------------------+-----------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
 | def             | yiibaidb       | before_employee_update | UPDATE             | def                  | yiibaidb            | employees          |            1 | NULL             | BEGIN
     INSERT INTO employees_audit
     SET action = 'update',
      employeeNumber = OLD.employeeNumber,
         lastname = OLD.lastname,
         changedat = NOW();
 END | ROW                | BEFORE        | NULL                       | NULL                       | OLD                      | NEW                      | 2017-08-02 22:06:36.40 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | root@localhost | utf8                 | utf8_general_ci      | utf8_general_ci    |
 +-----------------+----------------+------------------------+--------------------+----------------------+---------------------+--------------------+--------------+------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+---------------+----------------------------+----------------------------+--------------------------+--------------------------+------------------------+-----------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
 1 row in set
 SQL

 

 

MySQL SHOW TRIGGERS语句

在特定数据库中显示触发器的另外一种方法是使用SHOW TRIGGERS语句,以下所示:

 SHOW TRIGGERS [FROM|IN] database_name
 [LIKE expr | WHERE expr];
 SQL

例如,若是要查看当前数据库中的全部触发器,可使用SHOW TRIGGERS语句,以下所示:

 SHOW TRIGGERS;
 SQL

要获取特定数据库中的全部触发器,请在SHOW TRIGGERS语句中指定数据库名称,好比要查询数据库:yiibaidb下的全部触发器,以下所示:

 SHOW TRIGGERS FROM yiibaidb;
 SQL

上面语句返回yiibaidb数据库中的全部触发器。

要获取与特定表相关联的全部触发器,可使用SHOW TRIGGERS语句中的WHERE子句。 如下语句返回与employees表相关联的全部触发器:

 SHOW TRIGGERS FROM yiibaidb
 WHERE `table` = 'employees';
 SQL

请注意,咱们使用反引号包装table列,由于table是MySQL中的保留关键字。

当执行SHOW TRIGGERS语句时,MySQL返回如下列 -

  • Trigger:存储触发器的名称,例如before_employee_update触发器。

  • Event:指定事件,例如,调用触发器的INSERTUPDATEDELETE

  • Table:指定触发器与例如相关联的表,如employees表。

  • Statement:存储调用触发器时要执行的语句或复合语句。

  • Timing:接受两个值:BEFOREAFTER,它指定触发器的激活时间。

  • Created:在建立触发器时记录建立的时间。

  • sql_mode:指定触发器执行时的SQL模式。

  • Definer:记录建立触发器的账户。

请注意,要执行SHOW TRIGGERS语句,您必须具备SUPER权限。

删除触发器

要删除现有的触发器,请使用DROP TRIGGER语句,以下所示:

 DROP TRIGGER table_name.trigger_name;
 SQL

例如,若是要删除与employees表相关联的before_employees_update触发器,则能够执行如下语句:

 DROP TRIGGER employees.before_employees_update;
 SQL

要修改触发器,必须首先删除它并使用新的代码从新建立。在MySQL中没有相似:ALTER TRIGGER语句,所以,您不能像修改其余数据库对象,如视图存储过程那样修改触发器。

事件

MySQL事件调度器配置

MySQL使用一个名为事件调度线程的特殊线程来执行全部调度的事件。能够经过执行如下命令来查看事件调度程序线程的状态:

 SHOW PROCESSLIST;
 SQL

执行上面查询语句,获得如下结果 -

 mysql> SHOW PROCESSLIST;
 +----+------+-----------------+----------+---------+------+----------+------------------+
 | Id | User | Host            | db       | Command | Time | State    | Info             |
 +----+------+-----------------+----------+---------+------+----------+------------------+
 |  2 | root | localhost:50405 | NULL     | Sleep   | 1966 |          | NULL             |
 |  3 | root | localhost:50406 | yiibaidb | Sleep   | 1964 |          | NULL             |
 |  4 | root | localhost:50407 | yiibaidb | Query   |    0 | starting | SHOW PROCESSLIST |
 +----+------+-----------------+----------+---------+------+----------+------------------+
 3 rows in set
 Shell

默认状况下,事件调度程序线程未启用。 要启用和启动事件调度程序线程,须要执行如下命令:

 SET GLOBAL event_scheduler = ON;
 SQL

如今看到事件调度器线程的状态,再次执行SHOW PROCESSLIST命令,结果以下所示 -

 mysql> SHOW PROCESSLIST;
 +----+-----------------+-----------------+----------+---------+------+------------------------+------------------+
 | Id | User            | Host            | db       | Command | Time | State                  | Info             |
 +----+-----------------+-----------------+----------+---------+------+------------------------+------------------+
 |  2 | root            | localhost:50405 | NULL     | Sleep   | 1986 |                        | NULL             |
 |  3 | root            | localhost:50406 | yiibaidb | Sleep   | 1984 |                        | NULL             |
 |  4 | root            | localhost:50407 | yiibaidb | Query   |    0 | starting               | SHOW PROCESSLIST |
 |  5 | event_scheduler | localhost       | NULL     | Daemon  |    6 | Waiting on empty queue | NULL             |
 +----+-----------------+-----------------+----------+---------+------+------------------------+------------------+
 4 rows in set
 SQL

要禁用并中止事件调度程序线程,可经过执行SET GLOBAL命令将event_scheduler其值设置为OFF

 SET GLOBAL event_scheduler = OFF;
 SQL

建立新的MySQL事件

建立事件与建立其余数据库对象(如存储过程或触发器)相似。事件是一个包含SQL语句的命名对象。

存储过程仅在直接调用时执行; 触发器则与一个表相关联的事件(例如插入更新删除)事件发生时,能够在一次或更多的规则间隔执行事件时执行触发。

要建立和计划新事件,请使用CREATE EVENT语句,以下所示:

 CREATE EVENT [IF NOT EXIST] event_name
 ON SCHEDULE schedule
 DO
 event_body
 SQL

下面让咱们更详细地解释语法中的一些参数 -

  • 首先,在CREATE EVENT子句以后指定事件名称。事件名称在数据库模式中必须是惟一的。

  • 其次,在ON SCHEDULE子句后面加上一个表。若是事件是一次性事件,则使用语法:AT timestamp [+ INTERVAL],若是事件是循环事件,则使用EVERY子句:EVERY interval STARTS timestamp [+INTERVAL] ENDS timestamp [+INTERVAL]

  • 第三,将DO语句放在DO关键字以后。请注意,能够在事件主体内调用存储过程。 若是您有复合SQL语句,能够将它们放在BEGIN END块中。

咱们来看几个建立事件的例子来了解上面的语法。

首先,建立并计划将一个消息插入到messages表中的一次性事件,请执行如下步骤:

 USE testdb;
 CREATE TABLE IF NOT EXISTS messages (
     id INT PRIMARY KEY AUTO_INCREMENT,
     message VARCHAR(255) NOT NULL,
     created_at DATETIME NOT NULL
 );
 SQL

其次,使用CREATE EVENT语句建立一个事件:

 CREATE EVENT IF NOT EXISTS test_event_01
 ON SCHEDULE AT CURRENT_TIMESTAMP
 DO
   INSERT INTO messages(message,created_at)
   VALUES('Test MySQL Event 1',NOW());
 SQL

第三,检查messages表; 会看到有1条记录。这意味着事件在建立时被执行。

 SELECT * FROM messages;
 SQL

执行上面查询语句,获得如下结果 -

 mysql> SELECT * FROM messages;
 +----+--------------------+---------------------+
 | id | message            | created_at          |
 +----+--------------------+---------------------+
 |  1 | Test MySQL Event 1 | 2017-08-03 04:23:11 |
 +----+--------------------+---------------------+
 1 row in set
 Shell

要显示数据库(testdb)的全部事件,请使用如下语句:

 SHOW EVENTS FROM testdb;
 SQL

执行上面查询看不到任何行返回,由于事件在到期时自动删除。 在咱们的示例中,它是一次性的事件,在执行完成时就过时了。

要更改此行为,可使用ON COMPLETION PRESERVE子句。如下语句建立另外一个一次性事件,在其建立时间1分钟后执行,执行后不会被删除。

 CREATE EVENT test_event_02
 ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
 ON COMPLETION PRESERVE
 DO
    INSERT INTO messages(message,created_at)
    VALUES('Test MySQL Event 2',NOW());
 SQL

等待1分钟后,查看messages表,添加了另外一条记录:

 SELECT * FROM messages;
 SQL

执行上面查询语句,获得如下结果 -

 mysql> SELECT * FROM messages;
 +----+--------------------+---------------------+
 | id | message            | created_at          |
 +----+--------------------+---------------------+
 |  1 | Test MySQL Event 1 | 2017-08-03 04:23:11 |
 |  2 | Test MySQL Event 2 | 2017-08-03 04:24:48 |
 +----+--------------------+---------------------+
 2 rows in set
 Shell

若是再次执行SHOW EVENTS语句,看到事件是因为ON COMPLETION PRESERVE子句的影响:

 SHOW EVENTS FROM testdb;
 SQL

执行上面查询语句,获得如下结果 -

 mysql> SHOW EVENTS FROM testdb;
 +--------+---------------+----------------+-----------+----------+---------------------+----------------+----------------+--------+------+----------+------------+----------------------+----------------------+--------------------+
 | Db     | Name          | Definer        | Time zone | Type     | Execute at          | Interval value | Interval field | Starts | Ends | Status   | Originator | character_set_client | collation_connection | Database Collation |
 +--------+---------------+----------------+-----------+----------+---------------------+----------------+----------------+--------+------+----------+------------+----------------------+----------------------+--------------------+
 | testdb | test_event_02 | root@localhost | SYSTEM    | ONE TIME | 2017-08-03 04:24:48 | NULL           | NULL           | NULL   | NULL | DISABLED |          0 | utf8                 | utf8_general_ci      | utf8_general_ci    |
 +--------+---------------+----------------+-----------+----------+---------------------+----------------+----------------+--------+------+----------+------------+----------------------+----------------------+--------------------+
 1 row in set
 Shell

如下语句建立一个循环的事件,每分钟执行一次,并在其建立时间的1小时内过时:

 CREATE EVENT test_event_03
 ON SCHEDULE EVERY 1 MINUTE
 STARTS CURRENT_TIMESTAMP
 ENDS CURRENT_TIMESTAMP + INTERVAL 1 HOUR
 DO
    INSERT INTO messages(message,created_at)
    VALUES('Test MySQL recurring Event',NOW());
 SQL

请注意,使用STARTSENDS子句定义事件的有效期。等待个3,5分钟后再查看messages表数据,以测试验证此循环事件的执行。

 SELECT * FROM messages;
 SQL

执行上面查询语句,获得如下结果 -

 mysql> SELECT * FROM messages;
 +----+----------------------------+---------------------+
 | id | message                    | created_at          |
 +----+----------------------------+---------------------+
 |  1 | Test MySQL Event 1         | 2017-08-03 04:23:11 |
 |  2 | Test MySQL Event 2         | 2017-08-03 04:24:48 |
 |  3 | Test MySQL recurring Event | 2017-08-03 04:25:20 |
 |  4 | Test MySQL recurring Event | 2017-08-03 04:26:20 |
 |  5 | Test MySQL recurring Event | 2017-08-03 04:27:20 |
 +----+----------------------------+---------------------+
 5 rows in set
 Shell

删除MySQL事件

要删除现有事件,请使用DROP EVENT语句,以下所示:

 DROP EVENT [IF EXISTS] event_name;
 SQL

例如,要删除test_event_03的事件,请使用如下语句:

 DROP EVENT IF EXISTS test_event_03;

MySQL容许您更改现有事件的各类属性。要更改现有事件,请使用ALTER EVENT语句,以下所示:

 ALTER EVENT event_name
 ON SCHEDULE schedule
 ON COMPLETION [NOT] PRESERVE
 RENAME TO new_event_name
 ENABLE | DISABLE
 DO
  event_body
 SQL

请注意,ALTER EVENT语句仅适用于存在的事件。若是您尝试修改不存在的事件,MySQL将会发出一条错误消息,所以在更改事件以前,应先使用SHOW EVENTS语句检查事件的存在。

 SHOW EVENTS FROM testdb;
 SQL

执行上面查询,获得如下结果 -

 mysql> SHOW EVENTS FROM testdb;
 +--------+---------------+----------------+-----------+----------+---------------------+----------------+----------------+--------+------+----------+------------+----------------------+----------------------+--------------------+
 | Db     | Name         | Definer       | Time zone | Type     | Execute at         | Interval value | Interval field | Starts | Ends | Status   | Originator | character_set_client | collation_connection | Database Collation |
 +--------+---------------+----------------+-----------+----------+---------------------+----------------+----------------+--------+------+----------+------------+----------------------+----------------------+--------------------+
 | testdb | test_event_02 | root@localhost | SYSTEM   | ONE TIME | 2017-08-03 04:24:48 | NULL           | NULL           | NULL   | NULL | DISABLED |          0 | utf8                 | utf8_general_ci     | utf8_general_ci   |
 +--------+---------------+----------------+-----------+----------+---------------------+----------------+----------------+--------+------+----------+------------+----------------------+----------------------+--------------------+
 1 row in set
 Shell

ALTER EVENT示例

咱们建立一个示例事件来演示如何使用ALTER EVENT语句的各类功能。

如下语句建立一个事件,每分钟将一条新记录插入到messages表中。

 USE testdb;
 CREATE EVENT test_event_04
 ON SCHEDULE EVERY 1 MINUTE
 DO
    INSERT INTO messages(message,created_at)
    VALUES('Test ALTER EVENT statement',NOW());
 SQL

改变调度时间

要修改事件为每2分钟运行一次,请使用如下语句:

 ALTER EVENT test_event_04
 ON SCHEDULE EVERY 2 MINUTE;
 SQL

改变事件的主体代码逻辑

您还能够经过指定新的逻辑来更改事件的主体代码,以下所示:

 ALTER EVENT test_event_04
 DO
    INSERT INTO messages(message,created_at)
    VALUES('Message from event',NOW());
 -- 清空表中的数据
 truncate messages;
 SQL

上面修改完成后,能够等待2分钟,再次查看messages表:

 SELECT * FROM messages;
 SQL

执行上面查询,获得如下结果 -

 mysql> SELECT * FROM messages;
 +----+--------------------+---------------------+
 | id | message           | created_at         |
 +----+--------------------+---------------------+
 |  1 | Message from event | 2017-08-03 04:46:47 |
 |  2 | Message from event | 2017-08-03 04:48:47 |
 +----+--------------------+---------------------+
 2 rows in set
 Shell

禁用事件

要禁用某个事件,请在ALTER EVENT语句以后使用DISABLE关键字,请使用如下语句:

 ALTER EVENT test_event_04
 DISABLE;
 SQL

也能够经过使用SHOW EVENTS语句来查看事件的状态,以下所示:

 SHOW EVENTS FROM testdb;
 SQL

执行上面查询,获得如下结果 -

 mysql> SHOW EVENTS FROM testdb;
 +--------+---------------+----------------+-----------+-----------+---------------------+----------------+----------------+---------------------+------+----------+------------+----------------------+----------------------+--------------------+
 | Db     | Name         | Definer       | Time zone | Type     | Execute at         | Interval value | Interval field | Starts             | Ends | Status   | Originator | character_set_client | collation_connection | Database Collation |
 +--------+---------------+----------------+-----------+-----------+---------------------+----------------+----------------+---------------------+------+----------+------------+----------------------+----------------------+--------------------+
 | testdb | test_event_02 | root@localhost | SYSTEM   | ONE TIME | 2017-08-03 04:24:48 | NULL           | NULL           | NULL               | NULL | DISABLED |          0 | utf8                 | utf8_general_ci     | utf8_general_ci   |
 | testdb | test_event_04 | root@localhost | SYSTEM   | RECURRING | NULL               | 2             | MINUTE         | 2017-08-03 04:44:47 | NULL | DISABLED |          0 | utf8                 | utf8_general_ci     | utf8_general_ci   |
 +--------+---------------+----------------+-----------+-----------+---------------------+----------------+----------------+---------------------+------+----------+------------+----------------------+----------------------+--------------------+
 2 rows in set
 Shell

启用事件

要启用已禁用的事件,请在ALTER EVENT语句以后使用ENABLE关键字,以下所示:

 ALTER EVENT test_event_04
 ENABLE;
 SQL

查询上面语句执行结果,获得如下结果 -

 mysql> SHOW EVENTS FROM testdb;
 +--------+---------------+----------------+-----------+-----------+---------------------+----------------+----------------+---------------------+------+----------+------------+----------------------+----------------------+--------------------+
 | Db     | Name         | Definer       | Time zone | Type     | Execute at         | Interval value | Interval field | Starts             | Ends | Status   | Originator | character_set_client | collation_connection | Database Collation |
 +--------+---------------+----------------+-----------+-----------+---------------------+----------------+----------------+---------------------+------+----------+------------+----------------------+----------------------+--------------------+
 | testdb | test_event_02 | root@localhost | SYSTEM   | ONE TIME | 2017-08-03 04:24:48 | NULL           | NULL           | NULL               | NULL | DISABLED |          0 | utf8                 | utf8_general_ci     | utf8_general_ci   |
 | testdb | test_event_04 | root@localhost | SYSTEM   | RECURRING | NULL               | 2             | MINUTE         | 2017-08-03 04:44:47 | NULL | ENABLED |          0 | utf8                 | utf8_general_ci     | utf8_general_ci   |
 +--------+---------------+----------------+-----------+-----------+---------------------+----------------+----------------+---------------------+------+----------+------------+----------------------+----------------------+--------------------+
 2 rows in set
 Shell

重命名事件

MySQL不提供相似RENAME EVENT语句。幸运的是,咱们可使用ALTER EVENT重命名现有事件,以下所示:

 ALTER EVENT test_event_04
 RENAME TO test_event_05;
 SQL

查询上面语句执行结果,获得如下结果 -

 mysql> SHOW EVENTS FROM testdb;
 +--------+---------------+----------------+-----------+-----------+---------------------+----------------+----------------+---------------------+------+----------+------------+----------------------+----------------------+--------------------+
 | Db     | Name         | Definer       | Time zone | Type     | Execute at         | Interval value | Interval field | Starts             | Ends | Status   | Originator | character_set_client | collation_connection | Database Collation |
 +--------+---------------+----------------+-----------+-----------+---------------------+----------------+----------------+---------------------+------+----------+------------+----------------------+----------------------+--------------------+
 | testdb | test_event_02 | root@localhost | SYSTEM   | ONE TIME | 2017-08-03 04:24:48 | NULL           | NULL           | NULL               | NULL | DISABLED |          0 | utf8                 | utf8_general_ci     | utf8_general_ci   |
 | testdb | test_event_05 | root@localhost | SYSTEM   | RECURRING | NULL               | 2             | MINUTE         | 2017-08-03 04:44:47 | NULL | ENABLED |          0 | utf8                 | utf8_general_ci     | utf8_general_ci   |
 +--------+---------------+----------------+-----------+-----------+---------------------+----------------+----------------+---------------------+------+----------+------------+----------------------+----------------------+--------------------+
 2 rows in set
 Shell

将事件移动到其余数据库

能够经过使用RENAME TO子句将事件从一个数据库移动到另外一个数据库中,以下所示:

 ALTER EVENT testdb.test_event_05
 RENAME TO newdb.test_event_05;
 SQL

查询上面语句执行结果,获得如下结果 -

 mysql> SHOW EVENTS FROM newdb;
 +-------+---------------+----------------+-----------+-----------+------------+----------------+----------------+---------------------+------+---------+------------+----------------------+----------------------+--------------------+
 | Db   | Name         | Definer       | Time zone | Type     | Execute at | Interval value | Interval field | Starts             | Ends | Status | Originator | character_set_client | collation_connection | Database Collation |
 +-------+---------------+----------------+-----------+-----------+------------+----------------+----------------+---------------------+------+---------+------------+----------------------+----------------------+--------------------+
 | newdb | test_event_05 | root@localhost | SYSTEM   | RECURRING | NULL       | 2             | MINUTE         | 2017-08-03 04:44:47 | NULL | ENABLED |          0 | utf8                 | utf8_general_ci     | utf8_general_ci   |
 +-------+---------------+----------------+-----------+-----------+------------+----------------+----------------+---------------------+------+---------+------------+----------------------+----------------------+--------------------+
 1 row in set
 SQL

假设newdb数据库在MySQL数据库服务器中可用。

在本教程中,咱们向您展现了如何使用ALTER EVENT语句更改MySQL事件的各类属性。

视图

数据库视图是虚拟表或逻辑表,它被定义为具备链接的SQL SELECT查询语句。 由于数据库视图与数据库表相似,它由行和列组成,所以能够根据数据库表查询数据。 大多数数据库管理系统(包括MySQL)容许您经过具备一些先决条件的数据库视图来更新基础表中的数据。

 

 

 

 

数据库视图是动态的,由于它与物理模式无关。数据库系统将数据库视图存储为具备链接的SQL SELECT语句。当表的数据发生变化时,视图也反映了这些数据的变化。

数据库视图的优势

如下是使用数据库视图的优势 -

  • 数据库视图容许简化复杂查询:数据库视图由与许多基础表相关联的SQL语句定义。 您可使用数据库视图来隐藏最终用户和外部应用程序的基础表的复杂性。 经过数据库视图,您只需使用简单的SQL语句,而不是使用具备多个链接的复杂的SQL语句。

  • 数据库视图有助于限制对特定用户的数据访问。 您可能不但愿全部用户均可以查询敏感数据的子集。可使用数据库视图将非敏感数据仅显示给特定用户组。

  • 数据库视图提供额外的安全层。 安全是任何关系数据库管理系统的重要组成部分。 数据库视图为数据库管理系统提供了额外的安全性。 数据库视图容许您建立只读视图,以将只读数据公开给特定用户。 用户只能以只读视图检索数据,但没法更新。

  • 数据库视图启用计算列。 数据库表不该该具备计算列,但数据库视图能够这样。 假设在orderDetails表中有quantityOrder(产品的数量)和priceEach(产品的价格)列。 可是,orderDetails表没有一个列用来存储订单的每一个订单项的总销售额。若是有,数据库模式不是一个好的设计。 在这种状况下,您能够建立一个名为total的计算列,该列是quantityOrderpriceEach的乘积,以表示计算结果。当您从数据库视图中查询数据时,计算列的数据将随机计算产生。

  • 数据库视图实现向后兼容。 假设你有一个中央数据库,许多应用程序正在使用它。 有一天,您决定从新设计数据库以适应新的业务需求。删除一些表并建立新的表,而且不但愿更改影响其余应用程序。在这种状况下,能够建立与将要删除的旧表相同的模式的数据库视图。

数据库视图的缺点

除了上面的优势,使用数据库视图有几个缺点:

  • 性能:从数据库视图查询数据可能会很慢,特别是若是视图是基于其余视图建立的。

  • 表依赖关系:将根据数据库的基础表建立一个视图。每当更改与其相关联的表的结构时,都必须更改视图。

在MySQL中,视图的几乎特征符合SQL:2003标准。 MySQL以两种方式处理对视图的查询:

  • 第一种方式,MySQL会根据视图定义语句建立一个临时表,并在此临时表上执行传入查询。

  • 第二种方式,MySQL将传入查询与查询定义为一个查询并执行组合查询。

MySQL支持版本系统的视图。每次视图被更改或替换时,视图的副本将在驻留在特定数据库文件夹的arc(archive)文件夹中备份。备份文件的名称为view_name.frm-00001。若是再次更改视图,MySQL将建立一个名为view_name.frm-00002的新备份文件。

MySQL容许基于其余视图建立视图。在视图定义的SELECT语句中,能够引用另外一个视图。

MySQL视图的限制

不能在视图上建立索引。当使用合并算法的视图查询数据时,MySQL会使用底层表的索引。对于使用诱惑算法的视图,当您针对视图查询数据时,不会使用索引。

MySQL 5.7.7以前版本,是不能在SELECT语句的FROM子句中使用子查询来定义视图的。

若是删除或重命名视图所基于的表,则MySQL不会发出任何错误。可是,MySQL会使视图无效。 可使用CHECK TABLE语句来检查视图是否有效。

一个简单的视图能够更新表中数据。基于具备链接,子查询等的复杂SELECT语句建立的视图没法更新。

MySQL不像:OraclePostgreSQL等其余数据库系统那样支持物理视图,MySQL是不支持物理视图的。

CREATE VIEW语句简介

要在MySQL中建立一个新视图,可使用CREATE VIEW语句。 在MySQL中建立视图的语法以下:

 CREATE 
    [ALGORITHM = {MERGE | TEMPTABLE | UNDEFINED}]
 VIEW [database_name].[view_name]
 AS
 [SELECT statement]
 SQL

下面咱们来详细的查看上面的语法。

查看处理算法

算法属性容许您控制MySQL在建立视图时使用的机制,MySQL提供了三种算法:MERGETEMPTABLEUNDEFINED

  • 使用MERGE算法,MySQL首先将输入查询与定义视图的SELECT语句组合成单个查询。 而后MySQL执行组合查询返回结果集。 若是SELECT语句包含集合函数(如MINMAXSUMCOUNTAVG等)或DISTINCTGROUP BYHAVINGLIMITUNIONUNION ALL子查询,则不容许使用MERGE算法。 若是SELECT语句无引用表,则也不容许使用MERGE算法。 若是不容许MERGE算法,MySQL将算法更改成UNDEFINED。请注意,将视图定义中的输入查询和查询组合成一个查询称为视图分辨率

  • 使用TEMPTABLE算法,MySQL首先根据定义视图的SELECT语句建立一个临时表,而后针对该临时表执行输入查询。由于MySQL必须建立临时表来存储结果集并将数据从基表移动到临时表,因此TEMPTABLE算法的效率比MERGE算法效率低。 另外,使用TEMPTABLE算法的视图是不可更新的。

  • 当您建立视图而不指定显式算法时,UNDEFINED是默认算法。 UNDEFINED算法使MySQL能够选择使用MERGETEMPTABLE算法。MySQL优先使用MERGE算法进行TEMPTABLE算法,由于MERGE算法效率更高。

查看名称

在数据库中,视图和表共享相同的命名空间,所以视图和表不能具备相同的名称。 另外,视图的名称必须遵循表的命名规则。

SELECT语句

SELECT语句中,能够从数据库中存在的任何表或视图查询数据。SELECT语句必须遵循如下几个规则:

  • SELECT语句能够在WHERE子句中包含子查询,但FROM子句中的不能包含子查询。

  • SELECT语句不能引用任何变量,包括局部变量,用户变量和会话变量。

  • SELECT语句不能引用准备语句的参数。

请注意,SELECT语句不须要引用任何表。

建立MySQL视图示例

建立简单的视图

咱们来看看orderDetails表。基于orderDetails表来建立一个表示每一个订单的总销售额的视图。

 CREATE VIEW SalePerOrder AS
     SELECT
        orderNumber, SUM(quantityOrdered * priceEach) total
     FROM
        orderDetails
     GROUP by orderNumber
     ORDER BY total DESC;
 SQL

若是使用SHOW TABLES命令来查看示例数据库(yiibaidb)中的全部表,咱们还会看到SalesPerOrder视图也显示在表的列表中。以下所示 -

 mysql> SHOW TABLES;
 +--------------------+
 | Tables_in_yiibaidb |
 +--------------------+
 | article_tags       |
 | contacts           |
 | customers         |
 | departments       |
 | employees         |
 | offices           |
 | offices_bk         |
 | offices_usa       |
 | orderdetails       |
 | orders             |
 | payments           |
 | productlines       |
 | products           |
 | saleperorder       |
 +--------------------+
 14 rows in set
 SQL

这是由于视图和表共享相同的命名空间。要知道哪一个对象是视图或表,请使用SHOW FULL TABLES命令,以下所示:

 mysql> SHOW FULL TABLES;
 +--------------------+------------+
 | Tables_in_yiibaidb | Table_type |
 +--------------------+------------+
 | article_tags       | BASE TABLE |
 | contacts           | BASE TABLE |
 | customers         | BASE TABLE |
 | departments       | BASE TABLE |
 | employees         | BASE TABLE |
 | offices           | BASE TABLE |
 | offices_bk         | BASE TABLE |
 | offices_usa       | BASE TABLE |
 | orderdetails       | BASE TABLE |
 | orders             | BASE TABLE |
 | payments           | BASE TABLE |
 | productlines       | BASE TABLE |
 | products           | BASE TABLE |
 | saleperorder       | VIEW       |
 +--------------------+------------+
 14 rows in set
 SQL

结果集中的table_type列指定哪一个对象是视图,哪一个对象是一个表(基表)。如上所示,saleperorder对应table_type列的值为:VIEW

若是要查询每一个销售订单的总销售额,只须要对SalePerOrder视图执行一个简单的SELECT语句,以下所示:

 SELECT 
    *
 FROM
    salePerOrder;
 SQL

执行上面查询语句,获得如下结果 -

 +-------------+----------+
 | orderNumber | total   |
 +-------------+----------+
 |       10165 | 67392.85 |
 |       10287 | 61402.00 |
 |       10310 | 61234.67 |
 |       10212 | 59830.55 |
 |-- 此处省略了一大波数据-- |
 |       10116 | 1627.56 |
 |       10158 | 1491.38 |
 |       10144 | 1128.20 |
 |       10408 | 615.45   |
 +-------------+----------+
 327 rows in set
 SQL

基于另外一个视图建立视图

MySQL容许您基于另外一个视图建立一个视图。例如,能够根据SalesPerOrder视图建立名为大销售订单(BigSalesOrder)的视图,以显示总计大于60,000的每一个销售订单,以下所示:

 CREATE VIEW BigSalesOrder AS
     SELECT
        orderNumber, ROUND(total,2) as total
     FROM
        saleperorder
     WHERE
        total > 60000;
 SQL

如今,咱们能够从BigSalesOrder视图查询数据,以下所示:

 SELECT 
    orderNumber, total
 FROM
    BigSalesOrder;
 SQL

执行上面查询语句,获得如下结果 -

 +-------------+----------+
 | orderNumber | total   |
 +-------------+----------+
 |       10165 | 67392.85 |
 |       10287 | 61402.00 |
 |       10310 | 61234.67 |
 +-------------+----------+
 3 rows in set
 SQL

使用链接表建立视图

如下是使用INNER JOIN建立视图的示例。 该视图包含客户编号和客户支付的总金额。

 CREATE VIEW customerOrders AS
     SELECT
        c.customerNumber,
        p.amount
     FROM
        customers c
            INNER JOIN
        payments p ON p.customerNumber = c.customerNumber
     GROUP BY c.customerNumber
     ORDER BY p.amount DESC;
 SQL

要查询customerOrders视图中的数据,请使用如下查询:

 SELECT * FROM customerOrders;
 SQL

执行上面查询语句,获得如下结果 -

 +----------------+-----------+
 | customerNumber | amount   |
 +----------------+-----------+
 |            124 | 101244.59 |
 |            321 | 85559.12 |
 |            239 | 80375.24 |
 | **** 此处省略了一大波数据 ***|
 |            219 | 3452.75   |
 |            216 | 3101.4   |
 |            161 | 2434.25   |
 |            172 | 1960.8   |
 +----------------+-----------+
 98 rows in set
 Shell

使用子查询建立视图

如下说明如何使用子查询建立视图,该视图包含价格高于全部产品的平均价格的产品。

 CREATE VIEW aboveAvgProducts AS
     SELECT
        productCode, productName, buyPrice
     FROM
        products
     WHERE
        buyPrice >
  (SELECT
                AVG(buyPrice)
             FROM
                products)
     ORDER BY buyPrice DESC;
 SQL

查询上述视图:aboveAvgProducts的数据简单以下:

 SELECT 
    *
 FROM
    aboveAvgProducts;
 SQL

执行上面查询语句,获得如下结果 -

 +-------------+-----------------------------------------+----------+
 | productCode | productName                             | buyPrice |
 +-------------+-----------------------------------------+----------+
 | S10_4962   | 1962 LanciaA Delta 16V                 | 103.42   |
 | S18_2238   | 1998 Chrysler Plymouth Prowler         | 101.51   |
 | S10_1949   | 1952 Alpine Renault 1300               | 98.58   |
 |************* 此处省略了一大波数据 *********************************|
 | S18_3320   | 1917 Maxwell Touring Car               | 57.54   |
 | S24_4258   | 1936 Chrysler Airflow                   | 57.46   |
 | S18_3233   | 1985 Toyota Supra                       | 57.01   |
 | S18_2870   | 1999 Indy 500 Monte Carlo SS           | 56.76   |
 | S32_4485   | 1974 Ducati 350 Mk3 Desmo               | 56.13   |
 | S12_4473   | 1957 Chevy Pickup                       | 55.7     |
 | S700_3167   | F/A 18 Hornet 1/72                     | 54.4     |
 +-------------+-----------------------------------------+----------+
 54 rows in set

MySQL可更新视图简介

在MySQL中,视图不只是可查询的,并且是可更新的。这意味着您可使用INSERTUPDATE语句经过可更新视图插入或更新基表的行。 另外,您可使用DELETE语句经过视图删除底层表的行。

可是,要建立可更新视图,定义视图的SELECT语句不能包含如下任何元素:

若是使用TEMPTABLE算法建立视图,则没法更新视图。

请注意,有时可使用内部链接建立基于多个表的可更新视图。

MySQL可更新视图示例

让咱们先来看看如何建立一个可更新的视图。

首先,基于示例数据库(yiibaidb)中的offices表建立一个名为officeInfo的视图。该视图指的是offices表中的三列:officeCodephonecity

 CREATE VIEW officeInfo
  AS
    SELECT officeCode, phone, city
    FROM offices;
 SQL

接下来,使用如下语句从officeInfo视图中查询数据:

 SELECT 
    *
 FROM
    officeInfo;
 SQL

执行上面查询语句,获得如下结果 -

 mysql> SELECT * FROM officeInfo;
 +------------+------------------+---------------+
 | officeCode | phone           | city         |
 +------------+------------------+---------------+
 | 1         | +1 650 219 4782 | San Francisco |
 | 2         | +1 215 837 0825 | Boston       |
 | 3         | +1 212 555 3000 | NYC           |
 | 4         | +33 14 723 4404 | Paris         |
 | 5         | +86 33 224 5000 | Beijing       |
 | 6         | +61 2 9264 2451 | Sydney       |
 | 7         | +44 20 7877 2041 | London       |
 +------------+------------------+---------------+
 7 rows in set
 SQL

而后,使用如下UPDATE语句经过officeInfo视图更改officeCode的值为:4的办公室电话号码。

 UPDATE officeInfo 
 SET
    phone = '+86 089866668888'
 WHERE
    officeCode = 4;
 SQL

最后,验证更改结果,经过执行如下查询来查询officeInfo视图中的数据:

 mysql> SELECT 
    *
 FROM
    officeInfo
 WHERE
    officeCode = 4;
 
 +------------+------------------+-------+
 | officeCode | phone           | city |
 +------------+------------------+-------+
 | 4         | +86 089866668888 | Paris |
 +------------+------------------+-------+
 1 row in set
 Shell

检查可更新视图信息

经过从information_schema数据库中的views表查询is_updatable列来检查数据库中的视图是否可更新。

如下查询语句将查询yiibaidb数据库获取全部视图,并显示哪些视图是可更新的。

 SELECT 
    table_name, is_updatable
 FROM
    information_schema.views
 WHERE
    table_schema = 'yiibaidb';
 SQL

执行上面查询语句,获得如下结果 -

 +------------------+--------------+
 | table_name       | is_updatable |
 +------------------+--------------+
 | aboveavgproducts | YES         |
 | bigsalesorder   | YES         |
 | customerorders   | NO           |
 | officeinfo       | YES         |
 | saleperorder     | NO           |
 +------------------+--------------+
 5 rows in set
 SQL

经过视图删除行

首先,建立一个名为items的表,在items表中插入一些行,并建立一个查询包含价格大于700的项的视图。

 USE testdb;
 -- create a new table named items
 CREATE TABLE items (
     id INT AUTO_INCREMENT PRIMARY KEY,
     name VARCHAR(100) NOT NULL,
     price DECIMAL(11 , 2 ) NOT NULL
 );
 ​
 -- insert data into the items table
 INSERT INTO items(name,price) 
 VALUES('Laptop',700.56),('Desktop',699.99),('iPad',700.50) ;
 ​
 -- create a view based on items table
 CREATE VIEW LuxuryItems AS
     SELECT 
         *
     FROM
         items
     WHERE
         price > 700;
 -- query data from the LuxuryItems view
 SELECT 
     *
 FROM
     LuxuryItems;
 SQL

执行上面查询语句后,获得如下结果 -

 +----+--------+--------+
 | id | name   | price |
 +----+--------+--------+
 |  1 | Laptop | 700.56 |
 |  3 | iPad   | 700.5 |
 +----+--------+--------+
 2 rows in set
 Shell

其次,使用DELETE语句来删除id3的行。

 DELETE FROM LuxuryItems 
 WHERE
    id = 3;
 SQL

MySQL返回一条消息,表示有1行受到影响。

 Query OK, 1 row affected
 SQL

第三步,再次经过视图检查数据。

 mysql> SELECT * FROM LuxuryItems;
 +----+--------+--------+
 | id | name   | price  |
 +----+--------+--------+
 |  1 | Laptop | 700.56 |
 +----+--------+--------+
 1 row in set
 SQL

第四步,还能够从基表items查询数据,以验证DELETE语句是否实际删除了该行。

 mysql> SELECT  * FROM items;
 +----+---------+--------+
 | id | name    | price  |
 +----+---------+--------+
 |  1 | Laptop  | 700.56 |
 |  2 | Desktop | 699.99 |
 +----+---------+--------+
 2 rows in set
 SQL

如上面所示,ID3的行在基表中被删除。

WITH CHECK OPTION子句简介

有时候,建立一个视图来显示表的部分数据。然而,简单视图是可更新的,所以能够更新经过视图不可见的数据。此更新使视图不一致。为了确保视图的一致性,在建立或修改视图时使用WITH CHECK OPTION子句。

下面说明了WITH CHECK OPTION子句的语法 -

 CREATE OR REPLACE VIEW view_name 
 AS
  select_statement
  WITH CHECK OPTION;
 SQL

请注意,将分号(;)放在WITH CHECK OPTION子句的末尾,而不是在SELECT语句的末尾来定义视图。

咱们来看一下使用WITH CHECK OPTION子句的例子。

MySQL WITH CHECK OPTION子句示例

首先,咱们根据employees建立一个名为vps的视图,以显示其职位为VP的员工,例如VP MarketingVP Sales

 CREATE OR REPLACE VIEW vps AS
     SELECT
        employeeNumber,
        lastname,
        firstname,
        jobtitle,
        extension,
        email,
        officeCode,
        reportsTo
     FROM
        employees
     WHERE
        jobTitle LIKE '%VP%';
 SQL

接下来,使用如下语句从vps视图中查询数据:

 SELECT * FROM vps;
 SQL

执行上面查询语句,获得如下结果 -

 mysql> SELECT * FROM vps;
 +----------------+----------+-----------+--------------+-----------+----------------------+------------+-----------+
 | employeeNumber | lastname | firstname | jobtitle     | extension | email               | officeCode | reportsTo |
 +----------------+----------+-----------+--------------+-----------+----------------------+------------+-----------+
 |           1056 | Hill     | Mary     | VP Sales     | x4611     | mary.hill@yiibai.com | 1         |      1002 |
 |           1076 | Firrelli | Jeff     | VP Marketing | x9273     | jfirrelli@yiibai.com | 1         |      1002 |
 +----------------+----------+-----------+--------------+-----------+----------------------+------------+-----------+
 2 rows in set
 SQL

由于vps是一个简单的视图,所以它是可更新的。

而后,咱们经过vps视图将一行员工数据信息插入。

 INSERT INTO vps(employeeNumber,firstname,lastname,jobtitle,extension,email,officeCode,reportsTo)
 values(1703,'Lily','Bush','IT Manager','x9111','lilybush@yiiibai.com',1,1002);
 SQL

请注意,新建立的员工经过vps视图不可见,由于她的职位是IT经理,而不是VP。使用如下SELECT语句来验证它。

 SELECT * FROM employees WHERE employeeNumber=1703;
 SQL

执行上面语句,获得如下结果 -

 +----------------+-----------+-----------+-----------+-----------------------+------------+-----------+----------------------+
 | employeeNumber | lastName | firstName | extension | email                 | officeCode | reportsTo | jobTitle             |
 +----------------+-----------+-----------+-----------+-----------------------+------------+-----------+----------------------+
 |           1703 | Bush     | Lily     | x9111     | lilybush@yiiibai.com | 1         |      1002 | IT Manager           |
 |           1702 | Gerard   | Martin   | x2312     | mgerard@gmail.com     | 4         |      1102 | Sales Rep           |
 |           1625 | Kato     | Yoshimi   | x102     | ykato@gmail.com       | 5         |      1621 | Sales Rep           |
 |           1621 | Nishi     | Mami     | x101     | mnishi@gmail.com     | 5         |      1056 | Sales Rep           |
 SQL

但这可能不是咱们想要的,由于经过vps视图暴露VP员工,而不是其余员工。

为了确保视图的一致性,用户只能显示或更新经过视图可见的数据,则在建立或修改视图时使用WITH CHECK OPTION

让咱们修改视图以包括WITH CHECK OPTION选项。

 CREATE OR REPLACE VIEW vps AS
     SELECT
        employeeNumber,
        lastname,
        firstname,
        jobtitle,
        extension,
        email,
        officeCode,
        reportsTo
     FROM
        employees
     WHERE
        jobTitle LIKE '%VP%'
 WITH CHECK OPTION;
 SQL

请注意在CREATE OR REPLACE语句的结尾处加上WITH CHECK OPTION子句。

以后,再次经过vps视图将一行插入employees表中,以下所示:

 INSERT INTO vps(employeeNumber,firstname,lastname,jobtitle,extension,email,officeCode,reportsTo)
 VALUES(1704,'John','Minsu','IT Staff','x9112','johnminsu@yiibai.com',1,1703);
 SQL

此次MySQL拒绝插入并发出如下错误消息:

 Error Code: 1369 - CHECK OPTION failed 'yiibaidb.vps'
 SQL

最后,咱们经过vps视图将一个职位为SVP Marketing的员工插入employees表,看看MySQL是否容许这样作。

 INSERT INTO vps(employeeNumber,firstname,lastname,jobtitle,extension,email,officeCode,reportsTo)
 VALUES(1704,'John','Minsu','SVP Marketing','x9112','johnminsu@classicmodelcars.com',1,1076);
 SQL

MySQL发出1行受影响(Query OK, 1 row affected)。

能够经过根据vps视图查询数据来再次验证插入操做。

 SELECT * FROM vps;
 SQL

如上查询结果所示,它的确按预期工做了。

 mysql> SELECT * FROM vps;
 +----------------+----------+-----------+---------------+-----------+--------------------------------+------------+-----------+
 | employeeNumber | lastname | firstname | jobtitle     | extension | email                         | officeCode | reportsTo |
 +----------------+----------+-----------+---------------+-----------+--------------------------------+------------+-----------+
 |           1056 | Hill     | Mary     | VP Sales     | x4611     | mary.hill@yiibai.com           | 1         |      1002 |
 |           1076 | Firrelli | Jeff     | VP Marketing | x9273     | jfirrelli@yiibai.com           | 1         |      1002 |
 |           1704 | Minsu   | John     | SVP Marketing | x9112     | johnminsu@classicmodelcars.com | 1         |      1076 |
 +----------------+----------+-----------+---------------+-----------+--------------------------------+------------+-----------+
 3 rows in set

LOCAL&CASCADED检查范围介绍

当使用WITH CHECK OPTION子句建立视图时,MySQL会经过视图检查正在更改的每一个行,例如插入更新删除,以使其符合视图的定义。由于MySQL容许基于另外一个视图建立视图,它还会检查依赖视图中的规则以保持一致性。

为了肯定检查的范围,MySQL提供了两个选项:LOCALCASCADED。若是您没有在WITH CHECK OPTION子句中显式指定关键字,则MySQL默认使用CASCADED

MySQL与CASCADC检查选项

要了解使用CASCADED CHECK OPTION的效果,请参阅下面的例子。

首先,建立一个名为t1的表,其中只有一个名称为:c的列,它的数据类型为int

 USE testdb;
 CREATE TABLE t1 (
    c INT
 );
 SQL

接下来,基于t1表建立一个名为v1的视图,以选择值大于10的行记录。

 CREATE OR REPLACE VIEW v1 
 AS
     SELECT
        c
     FROM
        t1
     WHERE
        c > 10;
 SQL

由于没有指定WITH CHECK OPTION,因此如下语句即便不符合v1视图的定义也能够工做。

 INSERT INTO v1(c) VALUES (5);
 SQL

而后,基于v1视图建立v2视图。在v2视图中添加一个WITH CASCADED CHECK OPTION子句。

 CREATE OR REPLACE VIEW v2 
 AS
     SELECT
        c
     FROM
        v1
 WITH CASCADED CHECK OPTION;
 SQL

如今,经过v2视图在t1表中插入一个值为5的行。

 INSERT INTO v2(c) VALUES (5);
 SQL

MySQL发出如下错误消息:

 Error Code: 1369. CHECK OPTION failed 'testdb.v2'
 SQL

它失败了,由于它建立一个不符合v2视图定义的新行。

以后,咱们再建立一个基于v2的名为v3的新视图。

 CREATE OR REPLACE VIEW v3 
 AS
     SELECT
        c
     FROM
        v2
     WHERE
        c < 20;
 SQL

咱们经过v3视图插入一个新行到t1表中,值为8

 INSERT INTO v3(c) VALUES (8);
 SQL

MySQL发出如下错误信息:

 Error Code: 1369. CHECK OPTION failed 'testdb.v3'
 SQL

上面插入语句看起来符合v3视图的定义,insert语句仍然执行失败。

这是为何呢?

由于v3视图取决于v2视图,v2视图具备WITH CASCADED CHECK OPTION

可是,如下插入语句能正常工做。

 INSERT INTO v3(c) VALUES (30);
 SQL

由于v3视图没有使用WITH CHECK OPTION定义,而且该语句符合v2视图的定义。

因此,总而言之:

当视图使用WITH CASCADED CHECK OPTION时,MySQL会循环检查视图的规则以及底层视图的规则。

MySQL WITH LOCAL CHECK OPTION

下面将演示使用 WITH LOCAL CHECK OPTION 选项,使用上面相同的示例来查看差别。

首先,将v2视图更改成使用WITH LOCAL CHECK OPTIONS替代。

 ALTER VIEW v2 AS
     SELECT
        c
     FROM
        v1
 WITH LOCAL CHECK OPTION;
 SQL

其次,插入与上述示例相同的行。

 INSERT INTO v2(c) VALUES (5);
 SQL

它是能够成功执行的。

由于v2视图没有任何规则。 v2视图取决于v1视图。 可是,v1视图没有指定检查选项,所以MySQL跳过检查v1视图中的规则。

请注意,在使用WITH CASCADED CHECK OPTION建立的v2视图中,此语句失败。

第三,经过v3视图将相同的行插入t1表。

 INSERT INTO v3(c) VALUES (8);
 SQL

在这种状况下能够执行成功,由于MySQL视图中的WITH LOCAL CHECK OPTIONS选项没有检查v1视图的规则。 另外,请注意,在使用WITH CASCADED CHECK OPTION建立的v2视图示例中,此语句执行失败。

 

所以,若是视图使用WITH LOCAL CHECK OPTION,MySQL会检查WITH LOCAL CHECK OPTIONWITH CASCADED CHECK OPTION选项的视图规则。

与使用WITH CASCADED CHECK OPTION的视图不一样,MySQL检查全部依赖视图的规则。

注意,在MySQL 5.7.6以前,若是您使用带有WITH LOCAL CHECK OPTION的视图,MySQL只会检查当前视图的规则,而且不会检查底层视图的规则。

查看视图定义

MySQL提供了用于显示视图定义的SHOW CREATE VIEW语句。

如下是SHOW CREATE VIEW语句的语法:

 SHOW CREATE VIEW [database_name].[view_ name];
 SQL

要显示视图的定义,须要在SHOW CREATE VIEW子句以后指定视图的名称。

为了更好的演示,咱们先来建立一个视图

假设根据employees表建立一个简单的视图用来显示公司组织结构:

 USE yiibaidb;
 CREATE VIEW organization AS
     SELECT
        CONCAT(E.lastname, E.firstname) AS Employee,
        CONCAT(M.lastname, M.firstname) AS Manager
     FROM
        employees AS E
            INNER JOIN
        employees AS M ON M.employeeNumber = E.ReportsTo
     ORDER BY Manager;
 SQL

从以上视图中查询数据,获得如下结果 -

 mysql> SELECT * FROM organization;
 +------------------+------------------+
 | Employee         | Manager         |
 +------------------+------------------+
 | BondurLoui       | BondurGerard     |
 | CastilloPamela   | BondurGerard     |
 | JonesBarry       | BondurGerard     |
 | HernandezGerard | BondurGerard     |
 .......此处省略了一大波数据.......
 | KatoYoshimi     | NishiMami       |
 | KingTom         | PattersonWilliam |
 | MarshPeter       | PattersonWilliam |
 | FixterAndy       | PattersonWilliam |
 +------------------+------------------+
 24 rows in set
 SQL

要显示视图的定义,请使用SHOW CREATE VIEW语句以下:

 SHOW CREATE VIEW organization;
 SQL

还可使用任何纯文本编辑器(如记事本)显示视图的定义,以打开数据库文件夹中的视图定义文件。

例如,要打开organization视图定义,可使用如下路径找到视图定义文件:\data\yiibaidb\organization.frm

可是,不该该直接在.frm文件中修改视图的定义。

修改视图

MySQL提供两个语句,容许您修改现有视图:ALTER VIEWCREATE OR REPLACE VIEW

使用ALTER VIEW语句修改视图

建立视图后,可使用ALTER VIEW语句修改视图。

ALTER VIEW语句的语法相似于CREATE VIEW语句,除了CREATE关键字被ALTER关键字替换外,其它都同样。

 ALTER
  [ALGORITHM = {MERGE | TEMPTABLE | UNDEFINED}]
  VIEW [database_name]. [view_name]
    AS
  [SELECT statement]
 SQL

如下语句经过添加email列来演示如何修改organization视图。

 ALTER VIEW organization
   AS
   SELECT CONCAT(E.lastname,E.firstname) AS Employee,
          E.email AS employeeEmail,
          CONCAT(M.lastname,M.firstname) AS Manager
   FROM employees AS E
  INNER JOIN employees AS M
     ON M.employeeNumber = E.ReportsTo
   ORDER BY Manager;
 SQL

要验证更改,能够从organization视图中查询数据:

 SELECT 
    *
 FROM
    Organization;
 SQL

执行上面查询语句,获得如下结果 -

 Shell

使用CREATE OR REPLACE VIEW语句修改视图

ALTER VIEW语句外,还可使用CREATE OR REPLACE VIEW语句来建立或替换现有视图。若是一个视图已经存在,MySQL只会修改视图。若是视图不存在,MySQL将建立一个新的视图。

如下语句使用CREATE OR REPLACE VIEW语法根据employees表建立一个名称为v_contacts的视图:

 CREATE OR REPLACE VIEW v_contacts AS
     SELECT
        firstName, lastName, extension, email
     FROM
        employees;
 -- 查询视图数据
 SELECT * FROM v_contacts;
 SQL

执行上面查询语句,获得如下结果 -

 +-----------+-----------+-----------+--------------------------------+
 | firstName | lastName | extension | email                         |
 +-----------+-----------+-----------+--------------------------------+
 | Diane     | Murphy   | x5800     | dmurphy@yiibai.com             |
 | Mary     | Hill     | x4611     | mary.hill@yiibai.com           |
 | Jeff     | Firrelli | x9273     | jfirrelli@yiibai.com           |
 | William   | Patterson | x4871     | wpatterson@yiibai.com         |
 | Gerard   | Bondur   | x5408     | gbondur@gmail.com             |
 | Anthony   | Bow       | x5428     | abow@gmail.com                 |
 | Leslie   | Jennings | x3291     | ljennings@yiibai.com           |
 .............. 此处省略了一大波数据 ..................................
 | Martin   | Gerard   | x2312     | mgerard@gmail.com             |
 | Lily     | Bush     | x9111     | lilybush@yiiibai.com           |
 | John     | Minsu     | x9112     | johnminsu@classicmodelcars.com |
 +-----------+-----------+-----------+--------------------------------+
 25 rows in set
 Shell

假设您要将职位(jobtitle)列添加到v_contacts视图中,只需使用如下语句 -

 CREATE OR REPLACE VIEW v_contacts AS
     SELECT
        firstName, lastName, extension, email, jobtitle
     FROM
        employees;
 -- 查询视图数据
 SELECT * FROM v_contacts;
 SQL

执行上面查询语句后,能够看到添加一列数据 -

 +-----------+-----------+-----------+--------------------------------+----------------------+
 | firstName | lastName | extension | email                         | jobtitle             |
 +-----------+-----------+-----------+--------------------------------+----------------------+
 | Diane     | Murphy   | x5800     | dmurphy@yiibai.com             | President           |
 | Mary     | Hill     | x4611     | mary.hill@yiibai.com           | VP Sales             |
 | Jeff     | Firrelli | x9273     | jfirrelli@yiibai.com           | VP Marketing         |
 ................... 此处省略了一大波数据 ....................................................
 | Yoshimi   | Kato     | x102     | ykato@gmail.com               | Sales Rep           |
 | Martin   | Gerard   | x2312     | mgerard@gmail.com             | Sales Rep           |
 | Lily     | Bush     | x9111     | lilybush@yiiibai.com           | IT Manager           |
 | John     | Minsu     | x9112     | johnminsu@classicmodelcars.com | SVP Marketing       |
 +-----------+-----------+-----------+--------------------------------+----------------------+
 25 rows in set
 SQL

删除视图

建立视图后,可使用DROP VIEW语句将其删除。下面说明了DROP VIEW语句的语法:

 DROP VIEW [IF EXISTS] [database_name].[view_name]
 SQL

IF EXISTS是语句的可选子句,它容许您检查视图是否存在。它能够避免删除不存在的视图的错误。

例如,若是要删除organization视图,能够按以下所示使用DROP VIEW语句:

 DROP VIEW IF EXISTS organization;
 SQL

每次修改或删除视图时,MySQL会将视图定义文件备份到/database_name/arc/目录中。 若是您意外修改或删除视图,能够从/database_name/arc/文件夹获取其备份。

三.元数据获取

  • 3.1 show

MySQL5.7show结构

 show  databases;                          #查看全部数据库
 show tables;                              #查看当前库的全部表
 SHOW TABLES FROM                          #查看某个指定库下的表
 show create database world                #查看建库语句
 show create table world.city              #查看建表语句
 show grants for root@'localhost'        #查看用户的权限信息
 show charset;                           #查看字符集
 show collation                            #查看校对规则
 show processlist;                         #查看数据库链接状况
 show index from                           #表的索引状况
 show status                               #数据库状态查看
 SHOW STATUS LIKE '%lock%';                #模糊查询数据库某些状态
 SHOW VARIABLES                            #查看全部配置信息
 SHOW variables LIKE '%lock%';             #查看部分配置信息
 show engines                              #查看支持的全部的存储引擎
 show engine innodb status\G             #查看InnoDB引擎相关的状态信息
 show binary logs                       #列举全部的二进制日志
 show master status                     #查看数据库的日志位置信息
 show binlog evnets in                  #查看二进制日志事件
 show slave status \G                   #查看从库状态
 SHOW RELAYLOG EVENTS               #查看从库relaylog事件信息
 desc (show colums from city)      #查看表的列定义信息

3.2 information_schema

  • a.介绍

视图,查询元数据的方法

  • b.tables视图应用

 TABLE_SCHEMA    ---->表所在库
 TABLE_NAME      ---->表名
 ENGINE          ---->存储引擎
 TABLE_ROWS      ---->表的行数(粗略统计)
 AVG_ROW_LENGTH  ---->表中行的平均行(字节)(粗略统计)
 INDEX_LENGTH    ---->索引的占用空间大小(字节)(粗略统计)
 DATA_FREE       ---->碎片数
 TABLE_COMMENT   ---->表注释

例子:

  • 1.查询整个数据库中全部库和所对应的表信息

 库名                     表个数                表名
 world                   3                   city,a,b
 SELECT table_schema,count(*) ,GROUP_CONCAT(table_name)
 FROM information_schema.tables
 GROUP BY table_schema;
  • 2.统计每一个库的数据量大小

    -- 表的数据量=平均行长度*行数+索引长度

 SELECT table_schema,sum(TABLE_ROWS*AVG_ROW_LENGTH+INDEX_LENGTH)/1024 
 FROM information_schema.tables
 GROUP BY table_schema;

3.3concat()拼接语句

  • a. 查询一下业务数据库中,非InnoDB的表

 mysql> create table t2(id int)engine=myisam;
 SELECT table_schema , table_name ,engine FROM information_schema.tables 
 WHERE 
 table_schema NOT IN ('mysql','sys','information_schema','performance_schema') 
 AND ENGINE <>'innodb';  
 mysql> alter table t2 engine=innodb;
 mysql> alter table world.t3 engine=innodb;
 mysql> select concat(user,"@",host) from mysql.user;
 SELECT CONCAT("table_schema" , "table_name" ,"engine") FROM information_schema.tables 
 WHERE 
 table_schema NOT IN ('mysql','sys','information_schema','performance_schema') 
 AND ENGINE <>'innodb';
  • b. 将非InnoDB表批量替换为InnoDB

 SELECT concat("alter table ",table_schema,".",table_name," engine=innodb;") FROM information_schema.tables 
 WHERE 
 table_schema NOT IN ('mysql','sys','information_schema','performance_schema','world') 
 AND ENGINE <>'innodb' into outfile '/tmp/alter.sql' ; 
 ​
 [root@db01 ~]#mysqldump -uroot -p123 world city>/tmp/world_city.sql#备份/tmp/world_city.sql
 [root@db01 ~]#mysqldump -uroot -p123 world city >/databak/world_city.sql
 SELECT  concat("mysqldump -uroot -p123 ",table_schema," ",table_name," >/databak/",table_schema,"_",table_name,".sql") FROM information_schema.tables 
 WHERE 
 table_schema NOT IN ('mysql','sys','information_schema','performance_schema') into outfile '/tmp/bak.sh' ;  #导出到/tmp/bak.sh
 [root@db01 ~]# sh /tmp/bak.sh

3.4 COLUMNS

 TABLE_SCHEMA    ---->库名
 TABLE_NAME     ---->表名
 COLUMN_NAME     ---->列名
 DATA_TYPE       ---->数据类型
 COLUMN_KEY     ---->列键
 COLUMN_COMMENT ---->列注释
 mysql> select id,command from processlist where command='sleep'
 mysql>kill 10:\
 mysql>kill 11:\

四.索引及执行计划

1.介绍

相似于一本书中的目录,起到优化查询的做用(select,update,delete)

2.种类

 Btree(平衡多叉树):b-tree   b+tree(b*tree),优势:范围查找
 HASH :优势,比较适合随机的等值。
 Rtree
 FullText
 GIS 索引

二叉树

 

 

 

 原理:
1> 将数据存放在一个一个节点上;
2> 节点又分为三种节点:最上面的叫根节点,中间的叫分支节点,最下面的到底叫叶子节点。
3> 每一个分支节点有一个分支,或者两个分支。
缺点
二叉树存在不平衡的问题。

红黑树

 

 

 

原理: 就是平衡的二叉树。

 红黑树(Red-Black Tree)是二叉搜索树(Binary Search Tree)的一种改进。咱们知道二叉搜索树在最坏的状况下可能会变成一个链表(当全部节点按从小到大的顺序依次插入后)。
 而红黑树在每一次插入或删除节点以后都会花O(log N)的时间来对树的结构做修改,以保持树的平衡。也就是说,红黑树的查找方法与二叉搜索树彻底同样;插入和删除节点的的方法前半部分节与二叉搜索树彻底同样,然后半部分添加了一些修改树的结构的操做。

缺点: 查询数字的速度慢。

3.Btree的细分

Btree树

 

原理:

 B树(B-Tree,并非B“减”树,横杠为链接符,容易被误导)
 <br> 是一种多路搜索树(并非二叉的):
 1.定义任意非叶子结点最多只有M个儿子;且M>2;
 2.根结点的儿子数为[2, M];
 3.除根结点之外的非叶子结点的儿子数为[M/2, M];
 4.每一个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
 5.非叶子结点的关键字个数=指向儿子的指针个数-1;
 6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
 7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
 8.全部叶子结点位于同一层;

B-树的特性:

 1.关键字集合分布在整颗树中;
 2.任何一个关键字出现且只出如今一个结点中;
 3.搜索有可能在非叶子结点结束;
 4.其搜索性能等价于在关键字全集内作一次二分查找;
 5.自动层次控制;

注意:B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,若是命中则结束,不然进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已是叶子结点;

每次查找数据时,都去根节点查找数据,查找效率低下。

B+TREE

 

 

 

 

 

 一般所说的索引是指 B-Tree索引,它是目前关系型数据库中查找数据最为经常使用和有效的索引,大多数存储引擎都支持这种索引,且 MySQL 默认采用这种索引。使用 B-Tree 这个术语,是由于 MySQL 在 CREATE TABLE 或其它语句中使用这个关键字,
但实际上不一样的存储引擎可能使用不一样的数据结构,好比 InnoDB 就是使用的 B+Tree。B+Tree中的B是指balance,意为平衡。相对 Hash索引,B+Tree在查找单条记录的速度比不上 Hash索引,可是由于更适合排序等操做,因此它更受欢迎。毕竟不可能只对数据库进行单条记录的操做

注 : B+树索引 并不能找到一个给定键值的具体行,它找到的只是被查找数据行所在的页,接着数据库会把页读入到内存,再在内存中进行查找,最后获得要查找的数据

叶子节点只放数据,根节点和分支节点只放索引。

B+树特性:

 1.全部关键字都出如今叶子结点的链表中(稠密索引),且链表中的关键字刚好是有序的;
 2.不可能在非叶子结点命中;
 3.非叶子结点至关因而叶子结点的索引(稀疏索引),叶子结点至关因而存储(关键字)数据的数据层;
 4.更适合文件索引系统;
 5.叶子节点最下面的数据块指针直接指向下一个数据块的位置,适合按顺序范围查询,搜索必定的范围,速度很是快。
 6.全部的记录优先级一致,查询次数一致,效率一致。

B+Tree索引:顺序存储,每个叶子节点到根结点的距离是相同的;左前缀索引,适合查询范围类的数据

可使用B+Tree索引的查询类型:

 全值匹配:精确全部索引列,如:姓wang,名xiaochun,年龄30
 匹配最左前缀:即只使用索引的第一列,如:姓wang
 匹配列前缀:只匹配一列值开头部分,如:姓以w开头的
 匹配范围值:如:姓ma和姓wang之间
 精确匹配某一列并范围匹配另外一列:如:姓wang,名以x开头的只访问索引的查询

B+Tree索引的限制

 1> 如不从最左列开始,则没法使用索引,如:查找名为xiaochun,或姓为g结尾
 2> 不能跳过索引中的列:如:查找姓wang,年龄30的,只能使用索引第一列
 3> 若是查询中某个列是为范围查询,那么其右侧的列都没法再使用索引:如:姓wang,名x%,年龄30,只能利用姓和名上面的索引

特别提示:

 1> 索引列的顺序和查询语句的写法应相匹配,才能更好的利用索引
 2> 为优化性能,可能须要针对相同的列但顺序不一样建立不一样的索引来知足不一样类型的查询需求

冗余和重复索引:

 冗余索引:(A),(A,B)
 重复索引:已经有索引,再次创建索引

索引优化策略:

 1> 独立地使用列:尽可能避免其参与运算,独立的列指索引列不能是表达式的一部分,也不能是函数的参数,在where条件中,始终将索引列单独放在比较符号的一侧
 2> 左前缀索引:构建指定索引字段的左侧的字符数,要经过索引选择性来评估
 3> 索引选择性:不重复的索引值和数据表的记录总数的比值
 4> 多列索引:AND操做时更适合使用多列索引,而非为每一个列建立单独的索引
 5> 选择合适的索引列顺序:无排序和分组时,将选择性最高放左侧

Hash 索引

 mysql 中,只有 Memory(Memory表只存在内存中,断电会消失,适用于临时表) 存储引擎显示支持 Hash索引,是 Memory表的默认索引类型,尽管 Memory表也可使用 B+Tree索引。Hash索引 把数据以 hash 形式组织起来,所以当查找某一条记录的时候,速度很是快。<br>可是由于 hash 结构,每一个键只对应一个值,并且是散列的方式分布,因此它并不支持范围查找和排序等功能

 

 

 

 聚簇和非聚簇索引,主键和二级索引

MyISAM:frm后缀的文件(存放表结构)、文件后缀是YMD(存放数据文件)和YMI后缀的文件(存放索引文件),索引和数据分开存放,为非聚簇索引

innodb:文件中frm后缀(存放表结构)和idb后缀的文件存放(索引和数据文件),索引和数据文件在一块儿存放,为聚簇索引

稠密索引:一个索引对应了全部的数据。

稀疏索引:一个索引对应了一个数据,且对应的数据不全。

简单索引:对单个字段建立索引

复合索引:对多个字段建立索引 

索引的设计原则

 1> 选择惟一性索引,可更快经过索引肯定某条记录
 2> 为常常须要 排序ORDER BY、分组GROUP BY 和 联合操做UNION 的字段创建索引
 3> 为常做为查询条件的字段创建索引
 4> 限制索引的数目,索引太多须要的磁盘空间就越大,修改表示对索引的重构和更新会很麻烦
 5> 尽可能使用数据量少的索引,对 CHAR(100) 全文索引确定会比 CHAR(10) 耗时多
 6> 尽可能使用前缀来索引
 7> 删除再也不使用或者不多使用的索引
 8> 避免多个范围条件 : MySQL 支持单列的范围索引,但不支持多列范围索引
 9> 尽可能避免NULL,含有 NULL 的索引将很难进行优化

索引优化建议

 1> 只要列中含有NULL值,就最好不要在此例设置索引,复合索引若是有NULL值,此列在使用时也不会使用索引
 2> 尽可能使用短索引,若是能够,应该制定一个前缀长度
 3> 对于常常在where子句使用的列,最好设置索引
 4> 对于有多个列where或者order by子句,应该创建复合索引
 5> 对于like语句,以%或者‘-’开头的不会使用索引,以%结尾会使用索引
 6> 尽可能不要在列上进行运算(函数操做和表达式操做)
 7> 尽可能不要使用not in和<>操做

SQL语句性能优化

 1> 查询时,能不要*就不用*,尽可能写全字段名
 2> 大部分状况链接效率远大于子查询
 3> 多表链接时,尽可能小表驱动大表,即小表 join 大表
 4> 在有大量记录的表分页时使用limit
 5> 对于常用的查询,能够开启缓存
 6> 多使用explain和profile分析查询语句
 7> 查看慢查询日志,找出执行时间长的sql语句优化

聚簇索引:主键索引

 (1)MySQL 会自动选择主键做为汇集索引列,没有主键会选择惟一键,若是都没有会生成隐藏的.
 (2)MySQL进行存储数据时,会按照汇集索引列值得顺序,有序存储数据行    
 (3)汇集索引直接将原表数据页,做为叶子节点,而后提取汇集索引列向上生成枝和根

辅助索引:

 (1) 提取索引列的全部值,进行排序      
 (2) 将排好序的值,均匀的存放在叶子节点,进一步生成枝节点和根节点
 (3) 在叶子节点中的值,都会对应存储主键ID
 单列
 联合
 惟一
 前缀

汇集索引和辅助索引的区别

 (1) 表中任何一个列均可以建立辅助索引,在你有须要的时候,只要名字不一样便可
 (2) 在一张表中,汇集索引只能有一个,通常是主键.
 (3) 辅助索引,叶子节点只存储索引列的有序值+汇集索引列值(id列值).
 (4) 汇集索引,叶子节点存储的是有序的整行数据.

4.索引的管理

#索引创建以前压测:

 mysqlslap --defaults-file=/etc/my.cnf \
 --concurrency=100 --iterations=1 --create-schema='test' \
 --query="select * from test.t100w where k2='VMlm'" engine=innodb \
 --number-of-queries=2000 -uroot -p123 -verbose
 mysql> alter table t100w add index idx_k2(k2);#优化索引
 Benchmark
     Running for engine rbose
     Average number of seconds to run all queries: 3.443 seconds
     Minimum number of seconds to run all queries: 3.443 seconds
     Maximum number of seconds to run all queries: 3.443 seconds
     Number of clients running queries: 100
     Average number of queries per client: 20
4.1 查询索引
 mysql> use information_schema;
 mysql> show tables;
 use world
 a.desc  city;
 PRI:  主键索引
 MUL:  普通索引
 UNI:  惟一索引
 b.mysql> show index from city;
 Table          表名
 Key_name       索引名
 Column_name    列名
 Cardinality    基数(选择度),位置值的多少

 

面试题:

Cardinality 创建索引以前,基数如何计算的?(重复值高于80%的不建议创建索引) select count(distinct countrycode)from city; +-----------------------------+ | count(distinct countrycode) | +-----------------------------+ | 232 | +-----------------------------+ mysql> select count(*)from city; mysql> select count(id)from city; +-----------+ | count(id) | +-----------+ | 4079 | +-----------+

4.2创建索引

a.单列索引

 mysql> alter table city add index i_name(name);

b.联合索引

 mysql> alter table city add index i_d_p(district,population);

c.前缀索引

 mysql> alter table city add index i_n(name(10));
 mysql> alter table city_demo add key(city(5));
 Query OK, 0 rows affected (0.34 sec)
 Records: 0  Duplicates: 0  Warnings: 0

 

d.惟一索引

 mysql> alter table tt add unique index ui(telnum);

去重复

 mysql> select count(distinct district) from city;
4.3删除索引
 mysql> show index from city;
 mysql> alter table city drop index i_name;
 mysql> alter table city drop index i_c_p;
 mysql> alter table city drop index i_n;

4.4 MySQL8.0新特性---> invisible index 不可见索引

 语法:
 ALTER TABLE t1 ALTER INDEX i_idx INVISIBLE;
 ALTER TABLE t1 ALTER INDEX i_idx VISIBLE;
 mysql> alter table city alter index idx_name invisible;
 SELECT INDEX_NAME, IS_VISIBLE
        FROM INFORMATION_SCHEMA.STATISTICS
        WHERE TABLE_SCHEMA = 'world' AND table_name='city';

5.B+tree查找算法介绍

 

 5.1 平衡

无论查找哪一个数,须要查找次数理论上是相同的,对于一个三层b树来说,理论上查找每一个值都是三次IO。

5.2擅长范围查找

讲究快速锁定范围

B+tree,加入了双向指针(头尾相接),进一步加强范围查找,减小对于ROOT和NO-LEAF的访问次数。

5.3构建过程

叶子:先将数据排序,生成叶子节点。

枝:保存叶子节点的范围(>=1 <5)+指针(→)

根:保存枝节点范围+指针

叶子节点和枝节点都有双向指针。

6.MySQL中如何应用B+TREE

6.1名词解释

 区(簇)extent:连续的64pages ,默认是1M存储空间
 page页:16KB大小,MySQL中最小的IO单元

6.2IOT组织表

数据应该按照索引结构有序(顺序)组织和存储数据

MySQL使用聚簇索引组织存储数据。

6.3 聚簇(区)索引

6.3.1 构建条件
 a. 若是表中有主键,主键就被做为聚簇索引.
 b. 没有主键,第一个不为空的惟一键.
 c. 什么都没有,自动生成一个6字节的隐藏列,做为聚簇索引.

https://dev.mysql.com/doc/refman/8.0/en/innodb-index-types.html

 When you define a PRIMARY KEY on your table, InnoDB uses it as the clustered index. Define a primary key for each table that you create. If there is no logical unique and non-null column or set of columns, add a new auto-increment column, whose values are filled in automatically.
 If you do not define a PRIMARY KEY for your table, MySQL locates the first UNIQUE index where all the key columns are NOT NULL and InnoDB uses it as the clustered index.
 If the table has no PRIMARY KEY or suitable UNIQUE index, InnoDB internally generates a hidden clustered index named GEN_CLUST_INDEX on a synthetic column containing row ID values. The rows are ordered by the ID that InnoDB assigns to the rows in such a table. The row ID is a 6-byte field that increases monotonically as new rows are inserted. Thus, the rows ordered by the row ID are physically in insertion order.
6.3.2 如何造成B树结构.
 叶子节点: 聚簇索引组织表,存数据时,已是按照ID列有序存储数据到各个连续的数据页中.原表数据存储结构就是叶子节点.
 枝节点:叶子节点中ID范围+指针
 根节点:枝节点的ID范围+指针
 叶子节点和枝节点都有双向指针.
6.3.3 优化了哪些查询?
 只能优化基于ID做为条件.索引单纯使用ID列查询,很局限.
 > < = group by
 where

 

 

 6.4 辅助索引

 

6.4.1 构建条件

须要人为按照需求建立辅助索引.

6.4.2 如何造成B树结构(面试题)

alter table t1 add index idx(name);

 叶子节点 : 将辅助索引列值(name)+ID提取出来,按照辅助索引列值从小到大排序,存储到各个page中,生成叶子节点.
 枝节点 : 存储了叶子节点中,name列范围+指针.
 根节点 : 枝节点的name的范围+指针.

 

 

 

 

 

 

6.4.3 优化了哪些查询?

若是查询条件使用了name列,都会先扫描辅助索引,得到ID,再回到聚簇索引(回表),按照ID进行聚簇索引扫描,最终获取到数据行.

 All indexes other than the clustered index are known as secondary indexes. In InnoDB, each record in a secondary index contains the primary key columns for the row, as well as the columns specified for the secondary index. InnoDB uses this primary key value to search for the row in the clustered index.
 If the primary key is long, the secondary indexes use more space, so it is advantageous to have a short primary key.

https://dev.mysql.com/doc/refman/8.0/en/innodb-index-types.htm

 

 

 

 

 

 

6.5 联合辅助索引结构

6.5.1 构建过程
 alter table t1 add index idx_n_g(a,b)
 叶子节点 : 提取a+b+id列值,按照a,b联合排序(从小到大),生成叶子节点.
 枝节点 : 叶子节点最左列范围+指针
 根节点 : 枝节点的范围+指针.

 

 

 

 回表作聚簇索引

 

 

 

 

 

 

6.5.2 优化了哪些查询?

查询条件中必须包含最左列条件(a),先经过 a条件 扫描联合索引的根节点和枝节点,从而获得叶子节点范围.再拿b做为条件过滤一次. 最终目的,获得更精确的ID .理论上减小回表的次数.

6.5.3 最左原则

创建联合索引时,选择基数大(重复值少)做为最左列. 查询条件中必需要包含最左列条件.

7.索引树高度

通常建议3-4层为准,3层b树,2000w+

a.数据行多

 分区表
 按期归档:通常按照时间字段,按期归档到历史库中。pt-archiver
 分库分表:分布式

b.索引列长度过长

 前缀索引

c.数据类型

 足够
 简短的
 合适的

影响索引树高度的因素

  1. 数据量级,解决方法:

    分区表:

    按期归档:通常按照时间字段,按期归档到历史库中, pt-archiver

    分库,分表,分布式

  2. 索引列值过长,解决方法:

    业务容许,尽可能选择字符长度短的列做为索引列

    业务不容许,采用前缀索引.

  3. 数据类型:

    变长长度字符串,使用了char,解决方案:变长长度字符串使用 varchar

    enum使用短字符代替长字符

     enum ('山东','河北','黑龙江','吉林','辽宁','陕西'......)
     enum (  1      2      3      4      5     6       )

8.回表问题

8.1回表是什么?

辅助索引扫描以后,获得ID,再回到聚簇索引查找的过程

8.2回表会带来什么问题?

 回表是随机IO,会致使IO的次数(IOPS)和量(吞吐量)增长
 IOPS(Input/Output Operations Per Second):每秒的读写次数,用于衡量随机访问的性能
 吞吐量:单位时间内成功地传送数据的数量(例如 200M/s)

8.3减小回表的方法

 a.建索引使用联合索引(覆盖),尽量多将查询条件的数据包含联合索引中
 b.精细查询条件(业务方面>and < ,limit)
 c.查询条件要符合联合索引规则。覆盖的列越多越好。
 a b   c
 where a=   and b=     and   c=

9.扩展项: 索引自优化AHI(自适应hash索引)\change buffer

AHI : 索引的索引. 为内存中的热点索引页,作了一个HASH索引表,可以快速找到须要的索引页地址. https://dev.mysql.com/doc/refman/8.0/en/innodb-adaptive-hash.html https://dev.mysql.com/doc/refman/8.0/en/innodb-architecture.html change buffer : 对于辅助索引的变化,不会当即更新到索引中.暂存至change buffer . https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html

10.分析执行计划

10.1 分析执行计划定义:

 优化器(算法)最终得出的,代价最低的,SQL语句的执行方案.

10.2 为何要分析执行计划?

 场景一: 分析比较慢的语句.
 场景二: 上线新业务,可能会包含不少select  update  delete...,提早发现问题.

10.3 如何抓取执行计划

a. 抓取目标

 select  update  delete

b. 方法

 mysql> desc   select * from world.city where countrycode='CHN';
 mysql> explain   select * from world.city where countrycode='CHN';
 +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
 | id   | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
 | ---- | ----------- | ----- | ---------- | ---- | ------------- | ---- | ------- | ---- | ---- | -------- | ----- |
 |      |             |       |            |      |               |      |         |      |      |          |       |
 +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
 | 1    | SIMPLE | city | NULL | ref  | CountryCode | CountryCode | 3    | const | 363  | 100.00 | NULL |
 | ---- | ------ | ---- | ---- | ---- | ----------- | ----------- | ---- | ----- | ---- | ------ | ---- |
 |      |        |      |      |      |             |             |      |       |      |        |      |
 +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+

 

type

从上往下效率依次升高

  1. ALL:全表扫描,不走索引

 ① 查询全表
 DESC SELECT * FROM world.city;
 ② 查询条件出现如下语句
 DESC SELECT * FROM world.city WHERE countrycode LIKE '%HN';
 ③ 辅助索引列的查询条件出现如下语句
 DESC SELECT * FROM world.city WHERE countrycode != 'CHN';
 DESC SELECT * FROM world.city WHERE countrycode <> 'CHN';
 DESC SELECT * FROM world.city WHERE countrycode NOT IN ('CHN','USA' );

注意:对于汇集索引,使用例③语句,依然会走RANGE索引

 DESC SELECT * FROM world.city WHERE id <> 10;
 DESC SELECT * FROM world.city WHERE id != 10;
 DESC SELECT * FROM world.city WHERE id NOT IN ('1','2' );
  1. INDEX:全索引扫描

 1. 查询须要获取整颗索引树,才能获得想要的结果时
 DESC  SELECT countrycode FROM world.city;
 2. 联合索引中,任何一个非最左列做为查询条件时
 idx_a_b_c(a,b,c) ---> a ab abc
 SELECT * FROM t1 WHERE b
 SELECT * FROM t1 WHERE c

RANGE:索引范围扫描

辅助索引列 > < >= <= LIKE IN OR

 DESC SELECT * FROM world.city WHERE Population > 1000000;
 DESC SELECT * FROM world.city WHERE Population < 100;
 DESC SELECT * FROM world.city WHERE Population >= 1000000;
 DESC SELECT * FROM world.city WHERE Population <= 100;
 DESC SELECT * FROM world.city WHERE countrycode LIKE 'CH%';
 DESC SELECT * FROM world.city WHERE countrycode IN ('CHN','USA');
 DESC SELECT * FROM world.city WHERE Population < 100 OR Population > 1000000;
  • 主键列 > < >= <= IN OR <> != NOT IN

     DESC SELECT * FROM world.city WHERE id<5;
     DESC SELECT * FROM world.city WHERE id>5;
     DESC SELECT * FROM world.city WHERE id<=5;
     DESC SELECT * FROM world.city WHERE id>=5;
     DESC SELECT * FROM world.city WHERE id IN ('1','2' );
     DESC SELECT * FROM world.city WHERE id < 5 OR id > 10;
     DESC SELECT * FROM world.city WHERE id <> 10;
     DESC SELECT * FROM world.city WHERE id != 10;
    

     

     DESC SELECT * FROM world.city WHERE id NOT IN ('1','2' );

注意:

IN不能享受到B+树的双向指针

 DESC SELECT * FROM world.city WHERE countrycode IN ('CHN','USA');

能够改写为

 DESC SELECT * FROM world.city WHERE countrycode='CHN'
 UNION ALL
 SELECT * FROM world.city WHERE countrycode='USA';

具体效果看压测结果

若是索引列的重复值太多, 改写为UNION没有意义

  1. ref:非惟一性索引,等值查询

 DESC SELECT * FROM world.city WHERE countrycode='CHN';
  1. eq_ref:在多表链接查询时,链接条件使用了主键或惟一键索引(uk pK)的非驱动表

 DESC SELECT b.name,a.name 
 FROM city a
 JOIN country b
 ON a.countrycode=b.code
 WHERE a.population <100;
  1. const(system):惟一索引的等值查询

 DESC SELECT * FROM world.city WHERE id=10;

10.4 如何分析执行计划

 table : 操做的表
 type   : 操做类型(全表\索引) ,ALL index range ref eq_ref const(system)
 possible_keys : 有可能用的索引
 key   : 真正要用是哪索引
 key_len: 索引覆盖长度(联合索引)
 rows   : 预估须要扫描的行数
 Extra : using where using index using index condition using filesort sort   using temp

EXPLAIN

1.经过EXPLAIN来分析索引的有效性

2.EXPLAIN SELECT clause

获取查询执行计划信息,用来查看查询优化器如何执行查询

输出信息说明: 参考 https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

id: 当前查询语句中,每一个SELECT语句的编号

 复杂类型的查询有三种:
 简单子查询
 用于FROM中的子查询
 联合查询:UNION

注意:UNION查询的分析结果会出现一个额外匿名临时表

3.select_type:简单查询为SIMPLE 复杂查询:

 SUBQUERY 简单子查询
 PRIMARY 最外面的SELECT
 DERIVED 用于FROM中的子查询
 UNION UNION语句的第一个以后的SELECT语句
 UNION RESULT 匿名临时表

table:SELECT语句关联到的表

4.type:关联类型或访问类型,即MySQL决定的如何去查询表中的行的方式,如下顺序,性能从低到高

 ALL: 全表扫描
 index:根据索引的次序进行全表扫描;若是在Extra列出现“Using index”表示了使用覆盖索引,而非全表扫描
 range:有范围限制的根据索引实现范围扫描;扫描位置始于索引中的某一点,结束于另外一点
 ref: 根据索引返回表中匹配某单个值的全部行
 eq_ref:仅返回一个行,但与须要额外与某个参考值作比较
 const, system: 直接返回单个行

5.possible_keys:查询可能会用到的索引 6.key: 查询中使用到的索引 7.key_len: 在索引使用的字节数

explain(desc)使用场景(面试题)

 题目意思:  咱们公司业务慢,请你从数据库的角度分析缘由
 1.mysql出现性能问题,我总结有两种状况:
 (1)应急性的慢:忽然夯住
 应急状况:数据库hang(卡了,资源耗尽)
 处理过程:
 1.show processlist; 获取到致使数据库hang的语句
 2. explain 分析SQL的执行计划,有没有走索引,索引的类型状况
 3. 建索引,改语句
 (2)一段时间慢(持续性的):
 (1)记录慢日志slowlog,分析slowlog
 (2)explain 分析SQL的执行计划,有没有走索引,索引的类型状况
 (3)建索引,改语句

10.5 type 详解

  • a. ALL 全表扫描

 mysql> explain  select * from world.city ;
 mysql> explain  select * from world.city where countrycode!='chn';
 mysql> explain   select * from world.city where countrycode like '%hn%';
 mysql> explain   select * from world.city where countrycode not in ('chn','usa');
  • b. index 全索引扫描

 须要扫描整颗索引树,才能获得想要的结果. 
 desc select id ,countrycode from world.city;
  • c. range 索引范围 是咱们应用索引优化的底线,也是应用最多的.

 mysql> desc select * from city where id<10;
 mysql> desc select * from city where countrycode like 'ch%';
 mysql> desc select * from city where countrycode in ('CHN','USA');

SQL 改写为:

 desc 
 select * from city where countrycode='CHN' union  all  select * from city where countrycode='USA'
 mysqlslap --defaults-file=/etc/my.cnf \
 --concurrency=100 --iterations=1 --create-schema='world' \
 --query=" select * from city where countrycode in ('CHN','USA')" engine=innodb \
 --number-of-queries=2000 -uroot -p123 -verbose
 mysqlslap --defaults-file=/etc/my.cnf \
 --concurrency=100 --iterations=1 --create-schema='world' \
 --query="select * from city where countrycode='CHN' union  all  select * from city where countrycode='USA'" engine=innodb \
 --number-of-queries=2000 -uroot -p123 -verbose

小经验:

 索引列基数多少 +  压测结果,最终评估是否须要使用union .

d. ref : 辅助索引等值查询

 mysql> desc  select * from city where countrycode='CHN';

e. eq_ref : 非驱动表,链接条件是主键或惟一键.

补充:多表链接时,小结果集的表驱动大表

优化会自动判断查询语句中的谁做为驱动表更合适,有可能会出现选择错误

咱们能够经过left join强制驱动表干预执行计划。

如何在任务判断一条链接语句中,谁是驱动表

 

a. 优化器的判断

 mysql>select count(*) from city  where city.population<100000;  
 mysql> desc select * from city join country on city.countrycode=country.code where city.population<100000  and country.SurfaceArea(国土面积 )>10000000;

b. 如何人为判断一条链接语句中,谁是驱动表 (工做中经常使用)

 mysql> select count(*) from country where country.SurfaceArea>10000000;
 +----------+
 | count(*) |
 +----------+
 |    2     |
 +----------+
 1 row in set (0.00 sec)
 ​
 mysql> select count(*) from city where city.population<100000 ;
 +----------+
 | count(*) |
 +----------+
 |   517    |
 +----------+

补充: 若是 where后的列中都有索引,会选择结果集小的做为驱动表.

c. 压测.

 mysql> desc select a.name, b.name,a.countrycode,a.population  
 from city  as a 
 join country as b 
 on a.countrycode=b.code where a.population<100;

f. const(system)

 mysql> desc select * from city where id=1;

10.6 key_len详解

a.介绍

(联合)索引覆盖长度

idx(a,b,c) ------->a(10) b(20) c(30)

b.如何计算索引列的key_len

key_len和每一个列的最大预留长度(字节)有关。

数据类型 utf8mb4 没有 not null
tinyint 1 1
int 4 1
char(n) 4*n 1
varchar(n) 4*n+2 1

mysql> desc select * from t100w where num=10 and k1 ='aa' and k2='bb';

+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | t100w | NULL | ref | idx_k2,idx | idx_k2 | 17 | const | 1 | 5.00 | Using where | +----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+ 1 row in set, 1 warning (1.65 sec)

c.联合索引应用细节

idx(a,b,c)

彻底覆盖

a= and b= and c=
a= and c= and b= 等值打乱顺序的
a= and b= and c范围
a= anb b字符范围 and c=

部分覆盖

a= and b=  
a=    
a= and b= and c=
a= and b数字范围 and c=

彻底不覆盖 bc ----> bc b

b

c

bc

cb

优化案例: idx(k1,num,k2)

 mysql> desc select * from t100w where k1='Vs' and num<27779  and k2='mnij' 

优化方案: 修改索引为idx(k1,k2,num)

mysql> desc select * from t100w where k1='Vs'  and k2='mnij' 

10.7 extra

 using index  使用了索引覆盖扫描 
 using where 使用where回表扫描数据行,说明目标表的索引没有设计好.
 a. table ----> 获取到出问题的表
 b. 看原始查询语句中的where条件列
 c. 查询列的索引状况-----> show index from t1;
 d. 按需优化索引.
 using filesort  使用了额外排序. 
 a. table ---->获取到出问题的表
 b. 查看原始语句中的: order by   group by   distinct
 c. 查看列的索引状况
 d. 按需优化索引.

优化案例:

 mysql> desc select *from city where countrycode='CHN' order by population;
 +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
 | id | select_type | table | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra         |
 +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
 |  1 | SIMPLE     | city | NULL       | ref | CountryCode   | CountryCode | 3       | const |  363 |   100.00 | Using filesort |
 +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
 mysql> show index from city;
 +-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
 | Table | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
 +-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
 | city |          0 | PRIMARY     |            1 | ID         | A         |        4188 |     NULL |   NULL |     | BTREE     |         |               | YES     | NULL       |
 | city |          1 | CountryCode |            1 | CountryCode | A         |         232 |     NULL |   NULL |     | BTREE     |         |               | YES     | NULL       |
 +-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
 mysql> alter table city add index idx(population);
 mysql> desc select *from city where countrycode='CHN' order by population;
 +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
 | id | select_type | table | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra         |
 +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
 |  1 | SIMPLE     | city | NULL       | ref | CountryCode   | CountryCode | 3       | const |  363 |   100.00 | Using filesort |
 +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
 mysql> alter table city add index idx_c_p(countrycode,population);
 Query OK, 0 rows affected (0.02 sec)
 Records: 0 Duplicates: 0 Warnings: 0
 mysql> desc select *from city where countrycode='CHN' order by population;
 +----+-------------+-------+------------+------+---------------------+---------+---------+-------+------+----------+-------+
 | id | select_type | table | partitions | type | possible_keys       | key     | key_len | ref   | rows | filtered | Extra |
 +----+-------------+-------+------------+------+---------------------+---------+---------+-------+------+----------+-------+
 |  1 | SIMPLE     | city | NULL       | ref | CountryCode,idx_c_p | idx_c_p | 3       | const |  363 |   100.00 | NULL |
 +----+-------------+-------+------------+------+---------------------+---------+---------+-------+------+----------+-------+

查看冗余索引

 mysql> select table_schema,table_name , redundant_index_name , redundant_index_columns  from sys.schema_redundant_indexes;
 using temp   --->  
 a. 条件范围是否是过大. 
 b. having  order by 额外排序
 c. 子查询

大概率开发须要改写语句了.

乱序查询

 

 

 

 

 

 

 辅助索引查找过程

 

 

 

 

11.扩展项: 关于索引的优化器算法:ICP \ MRR

11.1 ICP : Index Condition Pushdown

优化器算法: a. 查询优化器算法:

 mysql> select @@optimizer_switch;

b.设置优化器算法:

 mysql> set global optimizer_switch='index_condition_pushdown=off';

hits方式: https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html 配置文件: my.cnf 例子 :

 mysql> set global optimizer_switch='index_condition_pushdown=off';
 mysql> desc select * from t100w where k1='Vs'  and num<27779 and k2='mnij';
 +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
 | id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
 +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
 |  1 | SIMPLE      | t100w | NULL       | range | idx           | idx  | 14      | NULL |   29 |    10.00 | Using where |
 +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
 1 row in set, 1 warning (0.00 sec)
 mysql> set global optimizer_switch='index_condition_pushdown=on';
 ​
 mysql> desc select * from t100w where k1='Vs'  and num<27779 and k2='mnij';
 +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
 | id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                 |
 +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
 |  1 | SIMPLE      | t100w | NULL       | range | idx           | idx  | 14      | NULL |   29 |    10.00 | Using index condition |
 +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
 1 row in set, 1 warning (0.00 sec)

压测: a. 开ICP 20000次语句压测 索引顺序不调整

 mysqlslap --defaults-file=/etc/my.cnf \
 --concurrency=100 --iterations=1 --create-schema='test' \
 --query=" select * from t100w where k1='Vs'  and num<27779 and k2='mnij'" engine=innodb \
 --number-of-queries=20000 -uroot -p123 -verbose
 4.580 seconds
 4.569 seconds
 4.431 seconds
 4.433 seconds
 4.391 seconds

b. 关 ICP 20000次语句压测 索引顺序不调整

 5.327 
 5.516
 5.267
 5.330
 5.293 seconds

c. 索引顺序优化 压测

 4.251
 4.143

11.2 MRR

 MRR(Mean reciprocal rank)是一个国际上通用的对搜索算法进行评价的机制,即第一个结果匹配,分数为1,第二个匹配分数为0.5,第n个匹配分数为1/n,若是没有匹配的句子分数为0。最终的分数为全部得分之和。

https://dev.mysql.com/doc/refman/8.0/en/mrr-optimization.html

12.索引应用规范

 业务
 1.产品的功能
 2.用户的行为
 "热"查询语句 --->较慢--->slowlog
 "热"数据

12.1 创建索引的原则(DBA运维规范)

 (1) 必需要有主键,业务无关列。
 (2) 常常作为where条件列  order by  group by  join on, distinct 的条件(业务:产品功能+用户行为)
 (3) 最好使用惟一值多的列做为索引列,若是索引列重复值较多,能够考虑使用联合索引
 (4) 列值长度较长的索引列,咱们建议使用前缀索引.
 mysql> select count(distinct left(name,19)) from city;
 (5) 下降索引条目,一方面不要建立没用索引,不常使用的索引清理,percona toolkit(xxxxx)
 (6) 索引维护要避开业务繁忙期,建议用pt-osc。 
 (7) 联合索引最左原则

12.2 不走索引的状况(开发规范)

12.2.1 没有查询条件,或者查询条件没有创建索引

 select * from t1  ;
 select * from t1 where id=1001 or 1=1;

做业: SQL审核和审计. yearning.io github, inception

12.2.2 查询结果集是原表中的大部分数据,应该是15-25%以上。

查询的结果集,超过了总数行数25%,优化器以为就没有必要走索引了。 MySQL的预读功能有关。

能够经过精确查找范围,达到优化的效果。

 1000000
 500000 and

12.2.3 索引自己失效,统计信息不真实(过旧)

 索引有自我维护的能力。
 对于表内容变化比较频繁的状况下,有可能会出现索引失效。
 通常是删除重建

现象:

面试题

有一条select语句日常查询时很快,忽然有一天很慢,会是什么缘由 select? --->索引失效,统计数据不真实

 innodb_index_stats  索引统计信息
 innodb_table_stats  表的统计信息
 mysql> ANALYZE TABLE world.city;

12.2.4 查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等)

例子:

 错误的例子:select * from test where id-1=9;
 正确的例子:select * from test where id=10;
 算术运算
 函数运算
 子查询

12.2.5 隐式转换致使索引失效.这一点应当引发重视.也是开发中常常会犯的错误.

 这样会致使索引失效. 错误的例子
 mysql> alter table tab add index inx_tel(telnum);
 Query OK, 0 rows affected (0.03 sec)
 Records: 0  Duplicates: 0  Warnings: 0
 mysql> desc tab;
 +--------+-------------+------+-----+---------+-------+
 | Field  | Type        | Null | Key | Default | Extra |
 +--------+-------------+------+-----+---------+-------+
 | id    | int(11)    | YES  |    | NULL    |      |
 | name  | varchar(20) | YES  |    | NULL    |      |
 | telnum | varchar(20) | YES  | MUL | NULL    |      |
 +--------+-------------+------+-----+---------+-------+
 3 rows in set (0.01 sec)
 mysql> select * from tab where telnum='1333333';
 +------+------+---------+
 | id  | name | telnum  |
 +------+------+---------+
 |    1 | a    | 1333333 |
 +------+------+---------+
 1 row in set (0.00 sec)
 mysql> select * from tab where telnum=1333333;
 +------+------+---------+
 | id  | name | telnum  |
 +------+------+---------+
 |    1 | a    | 1333333 |
 +------+------+---------+
 1 row in set (0.00 sec)
 mysql> explain  select * from tab where telnum='1333333';
 +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
 | id | select_type | table | type | possible_keys | key    | key_len | ref  | rows | Extra                |
 +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
 ​
 |  1 | SIMPLE      | tab  | ref  | inx_tel      | inx_tel | 63      | const |    1 | Using index condition |
 +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
 1 row in set (0.00 sec)
 mysql> explain  select * from tab where telnum=1333333;
 +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
 | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra      |
 +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
 |  1 | SIMPLE      | tab  | ALL  | inx_tel      | NULL | NULL    | NULL |    2 | Using where |
 +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
 1 row in set (0.00 sec)
 mysql> explain  select * from tab where telnum=1555555;
 +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
 | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra      |
 +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
 |  1 | SIMPLE      | tab  | ALL  | inx_tel      | NULL | NULL    | NULL |    2 | Using where |
 +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
 1 row in set (0.00 sec)
 mysql> explain  select * from tab where telnum='1555555';
 +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
 | id | select_type | table | type | possible_keys | key    | key_len | ref  | rows | Extra                |
 +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
 |  1 | SIMPLE      | tab  | ref  | inx_tel      | inx_tel | 63      | const |    1 | Using index condition |
 +----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
 1 row in set (0.00 sec)
 mysql> desc select * from b where telnum=110;
 mysql> desc select * from b where telnum='110';

12.2.6 < > ,not in 不走索引(辅助索引)

 EXPLAIN  SELECT * FROM teltab WHERE telnum  <> '110';
 EXPLAIN  SELECT * FROM teltab WHERE telnum  NOT IN ('110','119');
 mysql> select * from tab where telnum <> '1555555';
 +------+------+---------+
 | id  | name | telnum  |
 +------+------+---------+
 |    1 | a    | 1333333 |
 +------+------+---------+
 1 row in set (0.00 sec)
 mysql> explain select * from tab where telnum <> '1555555';
 单独的>,<,in 有可能走,也有可能不走,和结果集有关,尽可能结合业务添加limit
 or或in  尽可能改为union
 EXPLAIN  SELECT * FROM teltab WHERE telnum  IN ('110','119');
 改写成:
 EXPLAIN SELECT * FROM teltab WHERE telnum='110'
 UNION ALL
 SELECT * FROM teltab WHERE telnum='119

12.2.7 like "%_" 百分号在最前面不走

 EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%'  走range索引扫描
 EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110'  不走索引
 %linux%类的搜索需求,可使用elasticsearch+mongodb 专门作搜索服务的数据库产品

Optimizer: 查询优化器

SQL语句在查询以前会使用查询优化器对查询进行优化。就是优化客户端请求的 query(sql语句) ,根据客户端请求的 query 语句,和数据库中的一些统计信息,在一系列算法的基础上进行分析,得出一个最优的策略,告诉后面的程序如何取得这个 query 语句的结果

他使用的是“选取-投影-联接”策略进行查询。 用一个例子就能够理解: select uid,name from user where gender = 1; 这个select 查询先根据where 语句进行选取,而不是先将表所有查询出来之后再进行gender过滤 这个select查询先根据uid和name进行属性投影,而不是将属性所有取出之后再进行过滤 将这两个查询条件联接起来生成最终查询结果

Cache和Buffer: 查询缓存

他的主要功能是将客户端提交 给MySQL 的 Select 类 query 请求的返回结果集 cache 到内存中,与该 query 的一个 hash 值 作 一个对应。该 Query 所取数据的基表发生任何数据的变化以后, MySQL 会自动使该 query 的Cache 失效。在读写比例很是高的应用系统中, Query Cache 对性能的提升是很是显著的。固然它对内存的消耗也是很是大的。

若是查询缓存有命中的查询结果,查询语句就能够直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。好比表缓存,记录缓存,key缓存,权限缓存等

存储引擎接口

存储引擎接口模块能够说是 MySQL 数据库中最有特点的一点了。目前各类数据库产品中,基本上只有 MySQL 能够实现其底层数据存储引擎的插件式管理。这个模块实际上只是 一个抽象类,但正是由于它成功地将各类数据处理高度抽象化,才成就了今天 MySQL 可插拔存储引擎的特点。

能够看出,MySQL区别于其余数据库的最重要的特色就是其插件式的表存储引擎。MySQL插件式的存储引擎架构提供了一系列标准的管理和服务支持,这些标准与存储引擎自己无关,多是每一个数据库系统自己都必需的,如SQL分析器和优化器等,而存储引擎是底层物理结构的实现,每一个存储引擎开发者均可以按照本身的意愿来进行开发。 注意:存储引擎是基于表的,而不是数据库。

查询的执行路径

 

 

MySQL 整个查询执行过程,总的来讲分为 5 个步骤 :

 1> 客户端向 MySQL 服务器发送一条查询请求
 2> 服务器首先检查查询缓存,若是命中缓存,则马上返回存储在缓存中的结果,不然进入下一阶段
 3> 服务器进行 SQL解析、预处理、再由优化器生成对应的执行计划
 4> MySQL 根据执行计划,调用存储引擎的 API来执行查询
 5> 将结果返回给客户端,同时缓存查询结果
相关文章
相关标签/搜索