小科普java
核心业务逻辑代码通常都是放在服务端的python
客户端容易被懂行点的人修改源码,形成损失mysql
***
一个查询语句的结果是虚拟表,将(查询出)这张虚拟表(的sql语句)保存下来,他就变成了一个视图(mysql中仍是以表的形式存在的)linux
当频繁须要用到多张表的联表结果,你就能够事先生成好视图,以后直接调用便可,避免了反复写联表操做的 sql 语句(实际效果至关于再次执行语句)web
# 语法 create view 视图名 as 生成虚拟表的查询语句 create view teacher_course as select * from teacher inner join course on teacher.tid = course.teacher_id; show tables # 便可看到该视图(当作表了) select * from teacher_course; # 看到的就是
视图中的数据自动更新了--> 执行查询视图记录语句 = 从新执行了建立视图的那个sql 语句算法
注意:sql
1.视图只有表结构,视图中的数据仍是来源于原来的表数据库
2.不要改动视图表中的数据(可能会报错,也可能会改其余表的数据)服务器
---> 我在cmd终端改动影响到了其余表
3.不要太依赖视图,尽可能少用视图来写业务逻辑
- 视图会影响性能,占用硬盘资源、数据库资源
- 工做中多是别的部门的人管理数据库,跨部门交流比较麻烦,并且若是别人改动了视图关联表,或删了视图那涉及到的业务逻辑就很危险了。
到达某个条件自动触发
当你在对数据进行增删改的状况下会自动触发触发器的运行
delimiter $$ # --> 默认 ; 改为了 $$
,只对当前窗口有效,从新登陆也会无效
delimiter $$ # 改变当前窗口sql 语句的结束符 create trigger 触发器的名字 after/before insert/update/delete on 表名 for each row begin # 操做其余表的sql 语句 end $$ delimiter ; # 把sql结束符改回 ; # 触发器常见命名格式:tri_before/after_insert/update/delete_表名
触发器里面的代码会触发触发器自己的执行,形成了死循环
在触发器里面千万不要写操做本表的语句,增删改查都不要,会报错
好比:user表的 新增前触发器 代码块内写的是 user表的新增插值操做,就会形成触发器死循环(直接报错)
create table user( id int primary key auto_increment, name varchar(32) not null, password varchar(255) not null ) create table log( id int primary key auto_increment, message varchar(255) ); drop trigger tri_after_insert_user; # 删除已存在的触发器 tri_after_insert_user delimiter $$ create trigger tri_after_insert_user after insert on user for each row begin # insert into user(name, password) values('老子翻车了', '123'); # 死循环了,像递归,不断触发这个触发器 # select * from user; # 也会报错,触发器里不能返回值 Not allowed to return a result set from a trigger insert into log(message) values ('看到我就说明你没翻车'); end $$ delimiter ; insert into user(name, password) values('会翻车吗', '可能吧'); select * from user; select * from log; # 删除触发器 drop trigger tri_after_insert_user;
摘抄自:mysql查看存储过程函数
# 查询数据库中的存储过程和函数 select `name` from mysql.proc where db = 'xx' and `type` = 'PROCEDURE' # 存储过程 select `name` from mysql.proc where db = 'xx' and `type` = 'FUNCTION' # 函数 show procedure status\G; # 存储过程(\G 竖式排版查看,横的太长了 看不清) show function status\G; # 函数 # 查看存储过程或函数的建立代码 show create procedure proc_name\G; show create function func_name\G; # 查看视图 SELECT * from information_schema.VIEWS; # 视图 SELECT * from information_schema.TABLES; # 表 # 查看触发器 SHOW TRIGGERS [FROM db_name] [LIKE expr] SELECT * FROM triggers T WHERE trigger_name="mytrigger"\G;
NEW 对象能够取到触发这个触发器的sql语句的记录对象,经过 . 字段名
的方式来获取到字段值
# 案例 (mysql大小写不敏感) CREATE TABLE cmd ( id INT PRIMARY KEY auto_increment, USER CHAR (32), priv CHAR (10), cmd CHAR (64), sub_time datetime, #提交时间 success enum ('yes', 'no') #0表明执行失败 ); CREATE TABLE errlog ( id INT PRIMARY KEY auto_increment, err_cmd CHAR (64), err_time datetime ); delimiter $$ # 将mysql默认的结束符由;换成$$ create trigger tri_after_insert_cmd after insert on cmd for each row begin if NEW.success = 'no' then # 新记录都会被MySQL封装成NEW对象 insert into errlog(err_cmd,err_time) values(NEW.cmd,NEW.sub_time); end if; end $$ delimiter ; # 结束以后记得再改回来,否则后面结束符就都是$$了 #往表cmd中插入记录,触发触发器,根据IF的条件决定是否插入错误日志 INSERT INTO cmd ( USER, priv, cmd, sub_time, success ) VALUES ('egon','0755','ls -l /etc',NOW(),'yes'), ('egon','0755','cat /etc/passwd',NOW(),'no'), ('egon','0755','useradd xxx',NOW(),'no'), ('egon','0755','ps aux',NOW(),'yes'); # 查询errlog表记录 select * from errlog; # 删除触发器 drop trigger tri_after_insert_cmd;
*****
事务包含一堆sql语句,要么所有成功,要么都不成功
用本身的话背下来
一个事务是一个不可分割的工做单位,事务中包括的诸操做要么都作,要么都不作。
事务必须是使数据库从一个一致性状态变到另外一个一致性状态。
一致性与原子性是密切相关的。
一个事务的执行不能被其余事务干扰。
即一个事务内部的操做及使用的数据对并发的其余事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
接下来的其余操做或故障不该该对其有任何影响。
start transaction
标志下面的语句都是 事务
事务开始后,只会在内存中修改
只有commit 以后才会写到硬盘上
rollback
事务开始的地方到这里的语句都会回滚(失效)
commit
把数据刷在硬盘上,后面再 rollback
就回滚不回去了
start transaction、rollback、commit 有点像python异常捕获的 try ... except ... else ...
create table user( id int primary key auto_increment, name char(32), balance int ); insert into user(name,balance) values ('wsb',1000), ('egon',1000), ('ysb',1000); # 修改数据以前先开启事务操做 start transaction; # 修改操做 update user set balance=900 where name='wsb'; #买支付100元 update user set balance=1010 where name='egon'; #中介拿走10元 update user set balance=1090 where name='ysb'; #卖家拿到90元 # 回滚到上一个状态 rollback; # 开启事务以后,只要没有执行commit操做,数据其实都没有真正刷新到硬盘 commit; """开启事务检测操做是否完整,不完整主动回滚到上一个状态,若是完整就应该执行commit操做""" # 站在python代码的角度,应该实现的伪代码逻辑, try: update user set balance=900 where name='wsb'; #买支付100元 update user set balance=1010 where name='egon'; #中介拿走10元 update user set balance=1090 where name='ysb'; #卖家拿到90元 except 异常: rollback; else: commit; # 那如何检测异常?
就相似于python中的自定义函数
内部封装了 sql 语句,后续想要实现相应的操做,只须要调用存储过程便可
# 无参数版 delimiter $$ # 改mysql的结束符 create procedure 存储结构名字() begin sql 语句; end delimiter ; # 改回来 call 存储结构名字() # 调用写好的存储过程 # 有参数版 delimiter $$ # 改mysql的结束符 create procedure 存储结构名字( in m int, # in 只能传进来,不能返回 in n int, # 参数对应的意思---> 进仍是出, 变量名, 数据类型 out res int, # out 只能返回,不能传 inout xxx int, # inout 能够传进来,也能够被返回 ) begin sql 语句; end delimiter ; # 改回来 call 存储结构名字(m, n) # 参数怎么传不知道。。
存储过程在哪一个库里定义就只能在哪一个库里面使用
delimiter $$ create procedure p1( in m int, # in 只能传进来,不能被返回 in n int, # 参数对应的意思---> 进仍是出, 变量名, 数据类型 out res int # out 只能被返回,不能传入 ) begin select tname from teacher where tid > m and tid < n; set res=0; # 就相似于一个标志位,用来标识存储器是否执行成功 end $$ delimiter ; show procedure status\G; # 查看存储过程(竖式展现排版)
set @res=10; # 设置全局变量 @res 等于10
,select @res; # 查看全局变量 @res 的值
call p1(1,5,@res);
将变量 @res
传入,以后能够经过 select @res
来查看存储过程执行完成后的返回结果
# 使用的是上一步建立的存储过程 import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='000000', database='day38', charset='utf8', autocommit=True, ) cursor = conn.cursor(pymysql.cursors.DictCursor) # -------------------------------------------- # cursor.callproc() 调用存储过程 # 内部自动用变量名存储对应值(看下面案例注释) # -------------------------------------------- cursor.callproc('p1', (1, 5, 10)) # 这里就不须要设置那个全局变量了(@res=10),内部自动用变量名存储了对应的值 print(cursor.fetchall()) # [{'tname': '李平老师'}, {'tname': '刘海燕老师'}, {'tname': '朱云海老师'}] ''' callproc 内部自动用变量名存储了对应的值 @_p1_0=1 @_p1_1=5 @_p1_2=10 # 自动取名规律: @_存储过程名_标号 ''' cursor.execute('select @_p1_0=1') print(cursor.fetchall()) # [{'@_p1_0=1': 1}] cursor.execute('select @_p1_1=5') print(cursor.fetchall()) # [{'@_p1_1=5': 1}] cursor.execute('select @_p1_2=10') print(cursor.fetchall()) # [{'@_p1_2=10': 0}]
监测一个事务是否成功,一般使用存储过程包起来
# 大前提:存储过程在哪一个库下面建立的只能在对应的库下面才能使用!!! delimiter // create PROCEDURE p5( OUT p_return_code tinyint ) BEGIN DECLARE exit handler for sqlexception BEGIN -- ERROR set p_return_code = 1; rollback; END; DECLARE exit handler for sqlwarning BEGIN -- WARNING set p_return_code = 2; rollback; END; START TRANSACTION; update user set balance=900 where id =1; update user123 set balance=1010 where id = 2; update user set balance=1090 where id =3; COMMIT; -- SUCCESS set p_return_code = 0; #0表明执行成功 END // delimiter ;
注意与存储过程的区别,mysql内置的函数只能在sql语句中使用!
参考博客:函数
1、数学函数 ROUND(x,y) 返回参数x的四舍五入的有y位小数的值 RAND() 返回0到1内的随机值,能够经过提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。 2、聚合函数(经常使用于GROUP BY从句的SELECT查询中) AVG(col)返回指定列的平均值 COUNT(col)返回指定列中非NULL值的个数 MIN(col)返回指定列的最小值 MAX(col)返回指定列的最大值 SUM(col)返回指定列的全部值之和 GROUP_CONCAT(col) 返回由属于一组的列值链接组合而成的结果 3、字符串函数 CHAR_LENGTH(str) 返回值为字符串str 的长度,长度的单位为字符。一个多字节字符算做一个单字符。 CONCAT(str1,str2,...) 字符串拼接 若有任何一个参数为NULL ,则返回值为 NULL。 CONCAT_WS(separator,str1,str2,...) 字符串拼接(自定义链接符) CONCAT_WS()不会忽略任何空字符串。 (然而会忽略全部的 NULL)。 CONV(N,from_base,to_base) 进制转换 例如: SELECT CONV('a',16,2); 表示将 a 由16进制转换为2进制字符串表示 FORMAT(X,D) 将数字X 的格式写为'#,###,###.##',以四舍五入的方式保留小数点后 D 位, 并将结果以字符串的形式返回。若 D 为 0, 则返回结果不带有小数点,或不含小数部分。 例如: SELECT FORMAT(12332.1,4); 结果为: '12,332.1000' INSERT(str,pos,len,newstr) 在str的指定位置插入字符串 pos:要替换位置其实位置 len:替换的长度 newstr:新字符串 特别的: 若是pos超过原字符串长度,则返回原字符串 若是len超过原字符串长度,则由新字符串彻底替换 INSTR(str,substr) 返回字符串 str 中子字符串的第一个出现位置。 LEFT(str,len) 返回字符串str 从开始的len位置的子序列字符。 LOWER(str) 变小写 UPPER(str) 变大写 REVERSE(str) 返回字符串 str ,顺序和字符顺序相反。 SUBSTRING(str,pos) , SUBSTRING(str FROM pos) SUBSTRING(str,pos,len) , SUBSTRING(str FROM pos FOR len) 不带有len 参数的格式从字符串str返回一个子字符串,起始于位置 pos。带有len参数的格式从字符串str返回一个长度同len字符相同的子字符串,起始于位置 pos。 使用 FROM的格式为标准 SQL 语法。也可能对pos使用一个负值。倘若这样,则子字符串的位置起始于字符串结尾的pos 字符,而不是字符串的开头位置。在如下格式的函数中能够对pos 使用一个负值。 mysql> SELECT SUBSTRING('Quadratically',5); -> 'ratically' mysql> SELECT SUBSTRING('foobarbar' FROM 4); -> 'barbar' mysql> SELECT SUBSTRING('Quadratically',5,6); -> 'ratica' mysql> SELECT SUBSTRING('Sakila', -3); -> 'ila' mysql> SELECT SUBSTRING('Sakila', -5, 3); -> 'aki' mysql> SELECT SUBSTRING('Sakila' FROM -4 FOR 2); -> 'ki' 4、日期和时间函数 CURDATE()或CURRENT_DATE() 返回当前的日期 CURTIME()或CURRENT_TIME() 返回当前的时间 DAYOFWEEK(date) 返回date所表明的一星期中的第几天(1~7) DAYOFMONTH(date) 返回date是一个月的第几天(1~31) DAYOFYEAR(date) 返回date是一年的第几天(1~366) DAYNAME(date) 返回date的星期名,如:SELECT DAYNAME(CURRENT_DATE); FROM_UNIXTIME(ts,fmt) 根据指定的fmt格式,格式化UNIX时间戳ts HOUR(time) 返回time的小时值(0~23) MINUTE(time) 返回time的分钟值(0~59) MONTH(date) 返回date的月份值(1~12) MONTHNAME(date) 返回date的月份名,如:SELECT MONTHNAME(CURRENT_DATE); NOW() 返回当前的日期和时间 QUARTER(date) 返回date在一年中的季度(1~4),如SELECT QUARTER(CURRENT_DATE); WEEK(date) 返回日期date为一年中第几周(0~53) YEAR(date) 返回日期date的年份(1000~9999) 重点: DATE_FORMAT(date,format) 根据format字符串格式化date值 mysql> SELECT DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y'); -> 'Sunday October 2009' mysql> SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s'); -> '22:23:00' mysql> SELECT DATE_FORMAT('1900-10-04 22:23:00', -> '%D %y %a %d %m %b %j'); -> '4th 00 Thu 04 10 Oct 277' mysql> SELECT DATE_FORMAT('1997-10-04 22:23:00', -> '%H %k %I %r %T %S %w'); -> '22 22 10 10:23:00 PM 22:23:00 00 6' mysql> SELECT DATE_FORMAT('1999-01-01', '%X %V'); -> '1998 52' mysql> SELECT DATE_FORMAT('2006-06-00', '%d'); -> '00' 5、加密函数 MD5() 计算字符串str的MD5校验和 PASSWORD(str) 返回字符串str的加密版本,这个加密过程是不可逆转的,和UNIX密码加密过程使用不一样的算法。 6、控制流函数 CASE WHEN[test1] THEN [result1]...ELSE [default] END 若是testN是真,则返回resultN,不然返回default CASE [test] WHEN[val1] THEN [result]...ELSE [default]END 若是test和valN相等,则返回resultN,不然返回default IF(test,t,f) 若是test是真,返回t;不然返回f IFNULL(arg1,arg2) 若是arg1不是空,返回arg1,不然返回arg2 NULLIF(arg1,arg2) 若是arg1=arg2返回NULL;不然返回arg1 7、控制流函数小练习 #7.一、准备表 /* Navicat MySQL Data Transfer Source Server : localhost_3306 Source Server Version : 50720 Source Host : localhost:3306 Source Database : student Target Server Type : MYSQL Target Server Version : 50720 File Encoding : 65001 Date: 2018-01-02 12:05:30 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for course -- ---------------------------- DROP TABLE IF EXISTS `course`; CREATE TABLE `course` ( `c_id` int(11) NOT NULL, `c_name` varchar(255) DEFAULT NULL, `t_id` int(11) DEFAULT NULL, PRIMARY KEY (`c_id`), KEY `t_id` (`t_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of course -- ---------------------------- INSERT INTO `course` VALUES ('1', 'python', '1'); INSERT INTO `course` VALUES ('2', 'java', '2'); INSERT INTO `course` VALUES ('3', 'linux', '3'); INSERT INTO `course` VALUES ('4', 'web', '2'); -- ---------------------------- -- Table structure for score -- ---------------------------- DROP TABLE IF EXISTS `score`; CREATE TABLE `score` ( `id` int(11) NOT NULL AUTO_INCREMENT, `s_id` int(10) DEFAULT NULL, `c_id` int(11) DEFAULT NULL, `num` double DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of score -- ---------------------------- INSERT INTO `score` VALUES ('1', '1', '1', '79'); INSERT INTO `score` VALUES ('2', '1', '2', '78'); INSERT INTO `score` VALUES ('3', '1', '3', '35'); INSERT INTO `score` VALUES ('4', '2', '2', '32'); INSERT INTO `score` VALUES ('5', '3', '1', '66'); INSERT INTO `score` VALUES ('6', '4', '2', '77'); INSERT INTO `score` VALUES ('7', '4', '1', '68'); INSERT INTO `score` VALUES ('8', '5', '1', '66'); INSERT INTO `score` VALUES ('9', '2', '1', '69'); INSERT INTO `score` VALUES ('10', '4', '4', '75'); INSERT INTO `score` VALUES ('11', '5', '4', '66.7'); -- ---------------------------- -- Table structure for student -- ---------------------------- DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `s_id` varchar(20) NOT NULL, `s_name` varchar(255) DEFAULT NULL, `s_age` int(10) DEFAULT NULL, `s_sex` char(1) DEFAULT NULL, PRIMARY KEY (`s_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of student -- ---------------------------- INSERT INTO `student` VALUES ('1', '鲁班', '12', '男'); INSERT INTO `student` VALUES ('2', '貂蝉', '20', '女'); INSERT INTO `student` VALUES ('3', '刘备', '35', '男'); INSERT INTO `student` VALUES ('4', '关羽', '34', '男'); INSERT INTO `student` VALUES ('5', '张飞', '33', '女'); -- ---------------------------- -- Table structure for teacher -- ---------------------------- DROP TABLE IF EXISTS `teacher`; CREATE TABLE `teacher` ( `t_id` int(10) NOT NULL, `t_name` varchar(50) DEFAULT NULL, PRIMARY KEY (`t_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of teacher -- ---------------------------- INSERT INTO `teacher` VALUES ('1', '大王'); INSERT INTO `teacher` VALUES ('2', 'alex'); INSERT INTO `teacher` VALUES ('3', 'egon'); INSERT INTO `teacher` VALUES ('4', 'peiqi'); #7.二、统计各科各分数段人数.显示格式:课程ID,课程名称,[100-85],[85-70],[70-60],[ <60] select score.c_id, course.c_name, sum(CASE WHEN num BETWEEN 85 and 100 THEN 1 ELSE 0 END) as '[100-85]', sum(CASE WHEN num BETWEEN 70 and 85 THEN 1 ELSE 0 END) as '[85-70]', sum(CASE WHEN num BETWEEN 60 and 70 THEN 1 ELSE 0 END) as '[70-60]', sum(CASE WHEN num < 60 THEN 1 ELSE 0 END) as '[ <60]' from score,course where score.c_id=course.c_id GROUP BY score.c_id;
CREATE TABLE blog ( id INT PRIMARY KEY auto_increment, NAME CHAR (32), sub_time datetime ); INSERT INTO blog (NAME, sub_time) VALUES ('第1篇','2015-03-01 11:31:21'), ('第2篇','2015-03-11 16:31:21'), ('第3篇','2016-07-01 10:21:31'), ('第4篇','2016-07-22 09:23:21'), ('第5篇','2016-07-23 10:11:11'), ('第6篇','2016-07-25 11:21:31'), ('第7篇','2017-03-01 15:33:21'), ('第8篇','2017-03-01 17:32:21'), ('第9篇','2017-03-01 18:31:21'); select date_format(sub_time,'%Y-%m'),count(id) from blog group by date_format(sub_time,'%Y-%m');
注意
函数中不要写sql语句(不然会报错),函数仅仅只是一个功能,是一个在sql中被应用的功能
若要想在begin...end...中写sql,请用存储过程
delimiter // create function f1( i1 int, i2 int) returns int BEGIN declare num int; set num = i1 + i2; return(num); END // delimiter ; # 在查询中使用函数 select f1(11,nid) ,name from tb2; # 删除函数 drop function func_name;
# if条件语句 delimiter // # 修改mysql 默认的语句结束符 CREATE PROCEDURE proc_if () BEGIN declare i int default 0; if i = 1 THEN SELECT 1; ELSEIF i = 2 THEN SELECT 2; ELSE SELECT 7; END IF; END // delimiter ; # 将sql语句默认结束符改回 ;
# while循环 delimiter // CREATE PROCEDURE proc_while () BEGIN DECLARE num INT ; SET num = 0 ; WHILE num < 10 DO SELECT num ; SET num = num + 1 ; END WHILE ; END // delimiter ;
**
mysql 默认有查询优化机制,咱们不须要再上面花多少精力,能优化的,mysql基本都直接给咱们优化了(可适当增长几个索引)
第八篇:索引原理与慢查询优化 ,写的很详细,案例也很充分,我这个写的很差😂
本篇仅做快速了解,第一遍最好是去看 egon 的博客(能有个详细的了解)感受写的着实不错,直接复制过来了。。。
索引是应用程序设计和开发的一个重要方面。
若索引太多,应用程序的性能可能会受到影响,而索引太少,对查询性能又会产生影响,要找到一个平衡点,这对应用程序的性能相当重要。
一些开发人员老是在过后才想起添加索引----我一直认为,这源于一种错误的开发模式,若是知道数据的使用,从一开始就应该在须要处添加索引。
开发人员每每对数据库的使用停留在应用的层面,好比编写SQL语句、存储过程之类,他们甚至可能不知道索引的存在,或认为过后让相关DBA加上便可,但DBA每每不够了解业务的数据流,而添加索引须要经过监控大量的SQL语句进而从中找到问题,这个步骤所需的时间确定是远大于初始添加索引所需的时间,而且可能会遗漏一部分的索引。
固然索引也并非越多越好,我曾经遇到过这样一个问题:
某台MySQL服务器io stat显示磁盘使用率一直处于100%,通过分析后发现是因为开发人员添加了太多的索引,在删除一些没必要要的索引以后,磁盘使用率立刻降低为20%。
可见索引的添加也是很是有技术含量的。
这一块了解个大概便可,能在写sql语句时稍微考虑下性能就能够了,毕竟我是要搞开发的呀~
索引的目的在于提升查询效率,与咱们查阅图书所用的目录是一个道理:先定位到章,而后定位到该章下的一个小节,而后找到页数。
索引在MySQL中也叫“键 key”, 是存储引擎用于快速找到记录的一种数据结构
数据库比起查字典,飞机航班等显然要复杂的多,由于不只面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。
那数据库应该选择怎么样的方式来应对全部的问题呢?咱们回想字典的例子,能不能把数据分红段,而后分段查询呢?
最简单的若是1000条数据,1到100分红第一段,101到200分红第二段,201到300分红第三段......这样查第250条数据,只要找第三段就能够了,一会儿去除了90%的无效数据。
但若是是1千万的记录呢,分红几段比较好?稍有算法基础的同窗会想到搜索树,其平均复杂度是lgN,具备不错的查询性能。
但这里咱们忽略了一个关键的问题,复杂度模型是基于每次相同的操做成原本考虑的。
而数据库实现比较复杂,一方面数据是保存在磁盘上的,另一方面为了提升性能,每次又能够把部分数据读入内存来计算,由于咱们知道访问磁盘的成本大概是访问内存的十万倍左右,因此简单的搜索树难以知足复杂的应用场景。
前面提到了访问磁盘,那么这里先简单介绍一下磁盘IO和预读。
磁盘读取数据靠的是机械运动,每次读取数据花费的时间能够分为寻道时间、旋转延迟、传输时间三个部分,
寻道时间指的是磁臂移动到指定磁道所须要的时间,主流磁盘通常在5ms如下;
旋转延迟就是咱们常常据说的磁盘转速,好比一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;
传输时间指的是从磁盘读出或将数据写入磁盘的时间,通常在零点几毫秒,相对于前两个时间能够忽略不计。
那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS(Million Instructions Per Second)的机器每秒能够执行5亿条指令,由于指令依靠的是电的性质,换句话说执行一次IO的时间能够执行约450万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。
下图是计算机硬件延迟的对比图,供你们参考:
考虑到磁盘IO是很是高昂的操做,计算机操做系统作了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,由于局部预读性原理告诉咱们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。
每一次IO读取的数据咱们称之为一页(page)。具体一页有多大数据跟操做系统有关,通常为4k或8k,也就是咱们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计很是有帮助。
上述三个键均可以加快查询,primary key 和 unique key 除索引外还有额外的约束
外键是用来建立表与表之间关联关系的,不算索引
索引必定是本身建的(key),普通字段没有 索引(以前我理解错了)
经过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,咱们能够老是用同一种查找方式来锁定数据。
虽然索引好用,但应该在合理范围内去用,并非越多越好
前面讲了索引的基本原理,数据库的复杂性,又讲了操做系统的相关知识,目的就是让你们了解,任何一种数据结构都不是凭空产生的,必定会有它的背景和使用场景,咱们如今总结一下:
咱们须要这种数据结构可以作些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。
那么咱们就想到若是一个高度可控的多路搜索树是否能知足需求呢?
就这样,b+树应运而生(B+树是经过二叉查找树,再由平衡二叉树,B树演化而来)。
如上图,是一颗b+树,关于b+树的定义能够参见B+树,这里只说一些重点.
浅蓝色的块咱们称之为一个磁盘块,能够看到每一个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),
如磁盘块1包含数据项17和35,包含指针P一、P二、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
真实的数据存在于叶子节点即三、五、九、十、1三、1五、2八、2九、3六、60、7五、7九、90、99。
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如1七、35并不真实存在于数据表中。
如图所示,若是要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找肯定29在17和35之间,锁定磁盘块1的P2指针,内存时间由于很是短(相比磁盘的IO)能够忽略不计,经过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,经过指针加载磁盘块8到内存,发生第三次IO,同时内存中作二分查找找到29,结束查询,总计三次IO。
真实的状况是,3层的b+树能够表示上百万的数据,若是上百万的数据查找只须要三次IO,性能提升将是巨大的,若是没有索引,每一个数据项都要发生一次IO,那么总共须要百万次的IO,显然成本很是很是高。
经过上面的分析,咱们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每一个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N必定的状况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,若是数据项占的空间越小,数据项的数量越多,树的高度越低。
这就是为何每一个数据项,即索引字段都要尽可能的小,好比int占4字节,要比bigint8字节少一半。这也是为何b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度降低,致使树增高。当数据项等于1时将会退化成线性表。
当b+树的数据项是复合的数据结构,好比(name,age,sex)的时候,b+数是按照从左到右的顺序来创建搜索树的,好比当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来肯定下一步的所搜方向,若是name相同再依次比较age和sex,最后获得检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪一个节点,由于创建搜索树的时候name就是第一个比较因子,必需要先根据name来搜索才能知道下一步去哪里查询。好比当(张三,F)这样的数据来检索时,b+树能够用name来指定搜索方向,但下一个字段age的缺失,因此只能把名字等于张三的数据都找到,而后再匹配性别是F的数据了, 这个是很是重要的性质,即索引的最左匹配特性。
只有叶子结点存放真实数据,根和树枝节点存的仅仅是虚拟数据
查询次数由树的层级决定,层级越低次数越少查询速度越快(磁盘块存的数据越多层级越少越容易拿到数据)
————> 这也是把 id 做为主键的缘由
一个磁盘块儿的大小是必定的,那也就意味着能存的数据量是必定的。
如何保证树的层级最低呢?一个磁盘块儿存放占用空间比较小的数据项
索引也有不一样的种类,按不一样的要求去分(不仅仅是一个字段做为索引)
汇集索引其实指的就是表的主键(通常都是 id字段)
InnoDB 引擎规定一张表中必需要有主键
InnoDB 在建表的时候对应到硬盘上是两个文件,
.frm 表结构文件
只存放表结构,不可能放索引,也就意味着 InnoDB 的索引跟数据都放在.ibd 表数据文件
中
汇集索引特色:叶子结点放的是一条条完整的记录
查询数据的时候不可能都是用 id 做为筛选条件,也可能会用 id 以外的 name,password 等字段信息,那么这个时候就没法利用到汇集索引的加速查询优点。
此时就须要给其余字段创建(的)索引,这些索引就叫辅助索引
辅助索引特色:叶子结点存放的是辅助索引字段对应的那条记录的主键的值(好比:按照name字段建立索引,那么叶子节点存放的是:{name对应的值:name所在的那条记录的主键值})找到后再拿着id 去汇集索引里面去查
叶子结点存放的是不是一整行的信息
InnoDB存储引擎支持覆盖索引(covering index,或称索引覆盖)
使用覆盖索引的一个好处是:辅助索引不包含整行记录的全部信息,故其大小要远小于汇集索引,所以能够减小大量的IO操做
select name from user where name='jason';
覆盖索引:只在辅助索引的叶子节点中就已经找到了全部咱们想要的数据(条件和要查的字段相同的时候)
where 条件后面的字段做为辅助索引,select 后面的字段正好是(没有多余的字段)要找的数据,若是辅助索引和要找的数据同样,那就找到了,就是覆盖索引 --> 我的解释,没有权威性
select age from user where name='jason';
虽然查询的时候查到了辅助索引name,可是要查的是age字段,因此查到name字段后还须要利用查到的汇集索引id 才去查找那条记录中 age 字段的值
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx'; # 若是上述四个字段区分度都很高,那给谁建都能加速查询 # 给email加然而不用email字段 select count(id) from s1 where name='jason' and gender = 'male' and id > 3; # 给name加然而不用name字段 select count(id) from s1 where gender = 'male' and id > 3; # 给gender加然而不用gender字段 select count(id) from s1 where id > 3; # 带来的问题是全部的字段都建了索引然而都没有用到,还须要花费四次创建的时间 create index idx_all on s1(email,name,gender,id); # 最左匹配原则,区分度高的往左放 select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx'; # 速度变快
查询优化神器-explain 经过分析sql语句来提高效率
设定一个时间检测全部超出改时间的sql语句,而后针对性的进行优化!
根据使用场景管理并设置合适的索引
摘自egon博客:,放本身博客方便看
#1. 准备表 create table s1( id int, name varchar(20), gender char(6), email varchar(50) ); #2. 建立存储过程,实现批量插入记录 delimiter $$ #声明存储过程的结束符号为$$ create procedure auto_insert1() BEGIN declare i int default 1; while(i<3000000)do insert into s1 values(i,'egon','male',concat('egon',i,'@oldboy')); set i=i+1; end while; END$$ #$$结束 delimiter ; #从新声明分号为结束符号 #3. 查看存储过程 show create procedure auto_insert1\G #4. 调用存储过程 call auto_insert1();
#无索引:mysql根本就不知道究竟是否存在id等于333333333的记录,只能把数据表从头至尾扫描一遍,此时有多少个磁盘块就须要进行多少IO操做,因此查询速度很慢 mysql> select * from s1 where id=333333333; Empty set (0.33 sec)
1. mysql先去索引表里根据b+树的搜索原理很快搜索到id等于333333333的记录不存在,IO大大下降,于是速度明显提高
2. 咱们能够去mysql的data目录下找到该表,能够看到占用的硬盘空间多了
3.须要注意,以下图
#1. 必定是为搜索条件的字段建立索引,好比select * from s1 where id = 333;就须要为id加上索引 #2. 在表中已经有大量数据的状况下,建索引会很慢,且占用硬盘空间,建完后查询速度加快 好比create index idx on s1(id);会扫描表中全部的数据,而后以id为数据项,建立索引结构,存放于硬盘的表中。 建完之后,再查询就会很快了。 #3. 须要注意的是:innodb表的索引会存放于s1.ibd文件中,而myisam表的索引则会有单独的索引文件table1.MYI MySAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在innodb中,表数据文件自己就是按照B+Tree(BTree即Balance True)组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,所以innodb表数据文件自己就是主索引。 由于inndob的数据文件要按照主键汇集,因此innodb要求表必需要有主键(Myisam能够没有),若是没有显式定义,则mysql系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则mysql会自动为innodb表生成一个隐含字段做为主键,这字段的长度为6个字节,类型为长整型.
并非说咱们建立了索引就必定会加快查询速度, 若想利用索引达到预想的提升查询速度的效果,咱们在添加索引时,必须遵循如下问题
范围问题,或者说条件不明确,条件中出现这些符号或关键字:>、>=、<、<=、!= 、between...and...、like、
尽可能选择区分度高的列做为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大咱们扫描的记录数越少,惟一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不一样,这个值也很难肯定,通常须要join的字段咱们都要求是0.1以上,即平均1条扫描10条记录
#先把表中的索引都删除,让咱们专心研究区分度的问题 mysql> desc s1; +--------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------+-------------+------+-----+---------+-------+ | id | int(11) | YES | MUL | NULL | | | name | varchar(20) | YES | | NULL | | | gender | char(5) | YES | | NULL | | | email | varchar(50) | YES | MUL | NULL | | +--------+-------------+------+-----+---------+-------+ rows in set (0.00 sec) mysql> drop index a on s1; Query OK, 0 rows affected (0.20 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> drop index d on s1; Query OK, 0 rows affected (0.18 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> desc s1; +--------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(20) | YES | | NULL | | | gender | char(5) | YES | | NULL | | | email | varchar(50) | YES | | NULL | | +--------+-------------+------+-----+---------+-------+ rows in set (0.00 sec) #先把表中的索引都删除,让咱们专心研究区分度的问题
咱们编写存储过程为表s1批量添加记录,name字段的值均为egon,也就是说name这个字段的区分度很低(gender字段也是同样的,咱们稍后再搭理它) 回忆b+树的结构,查询的速度与树的高度成反比,要想将树的高低控制的很低,须要保证:在某一层内数据项均是按照从左到右,从小到大的顺序依次排开,即左1<左2<左3<... 而对于区分度低的字段,没法找到大小关系,由于值都是相等的,毫无疑问,还想要用b+树存放这些等值的数据,只能增长树的高度,字段的区分度越低,则树的高度越高。极端的状况,索引字段的值都同样,那么b+树几乎成了一根棍。本例中就是这种极端的状况,name字段全部的值均为'egon' #如今咱们得出一个结论:为区分度低的字段创建索引,索引树的高度会很高,然而这具体会带来什么影响呢??? #1:若是条件是name='xxxx',那么确定是能够第一时间判断出'xxxx'是不在索引树中的(由于树中全部的值均为'egon’),因此查询速度很快 #2:若是条件正好是name='egon',查询时,咱们永远没法从树的某个位置获得一个明确的范围,只能往下找,往下找,往下找。。。这与全表扫描的IO次数没有多大区别,因此速度很慢
=和in能够乱序,好比a = 1 and b = 2 and c = 3 创建(a,b,c)索引能够任意顺序,mysql的查询优化器会帮你优化成索引能够识别的形式
索引列不能参与计算,保持列“干净”,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,缘由很简单,b+树中存的都是数据表中的字段值,但进行检索时,须要把全部元素都应用函数才能比较,显然成本太大。因此语句应该写成create_time = unix_timestamp(’2014-05-29’)
#一、and与or的逻辑 条件1 and 条件2:全部条件都成立才算成立,但凡要有一个条件不成立则最终结果不成立 条件1 or 条件2:只要有一个条件成立则最终结果就成立 #二、and的工做原理 条件: a = 10 and b = 'xxx' and c > 3 and d =4 索引: 制做联合索引(d,a,b,c) 工做原理: 对于连续多个and:mysql会按照联合索引,从左到右的顺序找一个区分度高的索引字段(这样即可以快速锁定很小的范围),加速查询,即按照d—>a->b->c的顺序 #三、or的工做原理 条件: a = 10 or b = 'xxx' or c > 3 or d =4 索引: 制做联合索引(d,a,b,c) 工做原理: 对于连续多个or:mysql会按照条件的顺序,从左到右依次判断,即a->b->c->d
在左边条件成立可是索引字段的区分度低的状况下(name与gender均属于这种状况),会依次往右找到一个区分度高的索引字段,加速查询
通过分析,在条件为name='egon' and gender='male' and id>333 and email='xxx'的状况下,咱们彻底不必为前三个条件的字段加索引,由于只能用上email字段的索引,前三个字段的索引反而会下降咱们的查询效率
最左前缀匹配原则,是很是重要的原则,对于组合索引mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就中止匹配(指的是范围大了,有索引速度也慢),好比a = 1 and b = 2 and c > 3 and d = 4 若是创建(a,b,c,d)顺序的索引,d是用不到索引的,若是创建(a,b,d,c)的索引则均可以用到,a,b,d的顺序能够任意调整。
- 使用函数 select * from tb1 where reverse(email) = 'egon'; - 类型不一致 若是列是字符串类型,传入条件是必须用引号引发来,否则... select * from tb1 where email = 999; #排序条件为索引,则select字段必须也是索引字段,不然没法命中 - order by select name from s1 order by email desc; 当根据索引排序时候,select查询的字段若是不是索引,则速度仍然很慢 select email from s1 order by email desc; 特别的:若是对主键排序,则仍是速度很快: select * from tb1 order by nid desc; - 组合索引最左前缀 若是组合索引为:(name,email) name and email -- 命中索引 name -- 命中索引 email -- 未命中索引 - count(1)或count(列)代替count(*)在mysql中没有差异了 - create index xxxx on tb(title(19)) #text类型,必须制定长度
- 避免使用select * - count(1)或count(列) 代替 count(*) (默认是用id 汇集索引去查,效率会高不少) - 建立表时尽可能时 char 代替 varchar (自行选择执行效率仍是硬盘资源) - 表的字段顺序固定长度的字段优先 - 组合索引代替多个单列索引(常用多个条件查询时) - 尽可能使用短索引 (单个磁盘片上的数据多,层级少,查的快) - 使用链接(JOIN)来代替子查询(Sub-Queries) - 连表时注意条件类型需一致 - 索引散列值(重复少)不适合建索引,例:性别不适合(大量重复的,分层很差找)