小科普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) - 連表時注意條件類型需一致 - 索引散列值(重複少)不適合建索引,例:性別不適合(大量重複的,分層很差找)