目錄
事務 transaction
什么是事務
事務4個特性ACID
隔離級別
查詢mysql的隔離級別
事務處理
提交 commit
回滾 rollback
表強化:6約束 constraints
非空約束 not null
唯一約束 unique
主鍵約束 primary key
外鍵約束 foreign key
默認約束 default
檢查約束 check
索引 index
定義
分類
創建索引
索引掃描型別
最左特性
為何索引快?
小結
視圖View
概念
測驗
表關聯 association
概念
創建表
插入測驗資料
多表聯查 join
笛卡爾積 Cartesian product
三種連接 join
案例:列出research部門下的所有員工的資訊
案例:怎么用內鏈接 INNER JOIN 實作上面的需求?
案例:列出tony的擴展資訊
inner join、left join、right join的區別?
子查詢 subquery
概念
單行子查詢 =
多行子查詢 in
SQL面試題
SQL的執行順序
SQL優化
創建mysql-db庫
準備student表
準備tb_dept表
準備tb_user表
查詢SQL盡量不要使用select *,而是具體欄位
避免在where子句中使用or來連接條件
使用varchar代替char
盡量使用數值替代字串型別
查詢盡量避免回傳大量資料
使用explain分析你SQL執行計劃
是否使用了索引及其掃描型別
創建name欄位的索引
優化like陳述句
字串怪現象
索引不宜太多,一般5個以內
索引不適合建在有大量重復資料的欄位上
where限定查詢的資料
避免在where中對欄位進行運算式操作
避免在where子句中使用!=或<>運算子
去重distinct過濾欄位要少
where中使用默認值代替null
批量插入性能提升
批量洗掉優化
偽洗掉設計
提高group by陳述句的效率
復合索引最左特性
排序欄位創建索引
洗掉冗余和重復的索引
不要有超過5個以上的表連接
inner join 、left join、right join,優先使用inner join
in子查詢的優化
盡量使用union all替代union
事務 transaction
什么是事務
資料庫事務(Database Transaction),是指作為單個邏輯作業單元執行的一系列操作,要么完全地執行,要么完全地不執行,
簡單的說:事務就是將一堆的SQL陳述句(通常是增刪改操作)系結在一起執行,要么都執行成功,要么都執行失敗,即都執行成功才算成功,否則就會恢復到這堆SQL執行之前的狀態,
下面以銀行轉賬為例,A轉100塊到B的賬戶,這至少需要兩條SQL陳述句:
- 給A的賬戶減去100元;
update 賬戶表 set money=money**-100** where name='A';
- 給B的賬戶加上100元,
update 賬戶表 set money=money**+100** where name='B';
如果在第一條SQL陳述句執行成功后,在執行第二條SQL陳述句之前,程式被中斷了(可能是拋出了某個例外,也可能是其他什么原因),那么B的賬戶沒有加上100元,而A卻減去了100元,在現實生活中這肯定是不允許的,
如果在轉賬程序中加入事務,則整個轉賬程序中執行的所有SQL陳述句會在一個事務中,而事務中的所有操作,要么全都成功,要么全都失敗,不可能存在成功一半的情況,
也就是說給A的賬戶減去100元如果成功了,那么給B的賬戶加上100元的操作也必須是成功的;否則,給A減去100元以及給B加上100元都是失敗的,
事務4個特性ACID
一般來說,事務是必須滿足4個條件(ACID):原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨立性)、持久性(Durability),
- 原子性:一個事務(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節,事務在執行程序中如果發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣,
- 一致性:在事務開始之前和事務結束以后,資料庫的完整性沒有被破壞,這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及后續資料庫可以自發性地完成預定的作業,
- 隔離性:資料庫允許多個并發事務同時對其資料進行讀寫和修改的能力,隔離性可以防止多個事務并發執行時由于交叉執行而導致資料的不一致,
- 持久性:事務處理結束后,對資料的修改就是永久的,即便系統故障也不會丟失,
隔離級別
事務隔離分為不同級別,包括
- 讀未提交(Read uncommitted) 安全性最差,可能發生并發資料問題,性能最好
- 讀提交(read committed) Oracle默認的隔離級別
- 可重復讀(repeatable read)MySQL默認的隔離級別,安全性較好,性能一般
- 串行化(Serializable) 表級鎖,讀寫都加鎖,效率低下,安全性高,不能并發
查詢mysql的隔離級別
在默認情況下,MySQL每執行一條SQL陳述句,都是一個單獨的事務,如果需要在一個事務中包含多條SQL陳述句,那么需要手動開啟事務和結束事務,
- 開啟事務:start transaction;
- 結束事務:commit(提交事務)或rollback(回滾事務),
在執行SQL陳述句之前,先執行strat transaction,這就開啟了一個事務(事務的起點),然后可以去執行多條SQL陳述句,最后要結束事務,commit表示提交,即事務中的多條SQL陳述句所做出的影響會持久化到資料庫中,或者rollback,表示回滾,即回滾到事務的起點,之前做的所有操作都被撤消了!
SELECT @@tx_isolation;
Repeatable Read(可重讀)
MySQL的默認事務隔離級別,它確保同一事務的多個實體在并發讀取資料時,會看到同樣的資料行,
事務處理
- 在 MySQL 中只有使用了 Innodb 資料庫引擎的資料庫或表才支持事務
- 事務處理可以用來維護資料的完整性,保證成批的 SQL 陳述句要么全部執行,要么全部不執行
- 事務用來管理 insert、update、delete 陳述句,因為這些操作才會“破壞”資料,查詢select陳述句是不會的
- MySQL默認資料庫的事務是開啟的,執行SQL后自動提交,
- MySQL的事務也可以改成手動提交,那就有兩個步驟:先開啟,寫完SQL后,再手動提交,
提交 commit
#多條陳述句時,批量執行,事務提交
#有了事務,多步操作就形成了原子性操作,高并發下也不會引起資料錯亂
#mysql的事務默認就是開啟的 -- 多條陳述句一起操作時,要么一起成功要么一起失敗
BEGIN; #關閉事務的自動提交,相當于start transaction
INSERT INTO user (id) VALUES(25);#成功
INSERT INTO user (id) VALUES(5);#已經存在5了,會失敗
COMMIT; #手動提交事務
回滾 rollback
#多條陳述句,批量執行,insert插入重復的主鍵導致失敗時,事務回滾
BEGIN;
INSERT INTO user (id) VALUES(15);
INSERT INTO user (id) VALUES(35);#存在了
ROLLBACK;#事務回滾,就不會再提交了
表強化:6約束 constraints
非空約束 not null
DROP TABLE IF EXISTS tb_user; #如果表存在則洗掉,慎用會丟失資料
CREATE TABLE tb_user(
id INT AUTO_INCREMENT,
NAME VARCHAR(30) UNIQUE NOT NULL,
age INT,
phone VARCHAR(20) UNIQUE NOT NULL,
email VARCHAR(30) UNIQUE NOT NULL,
PRIMARY KEY (id)
);
DESC tb_user;
#id為自增主鍵,null值無效,資料庫會自動用下一個id值替代
#age因為運行為null,所以可以設定為null
INSERT INTO tb_user (id,age) VALUES(NULL,NULL);
唯一約束 unique
Name欄位創建了唯一約束,插入資料時資料庫會進行檢查,如果插入的值相同,就會檢查報錯:
DROP TABLE IF EXISTS tb_user; #如果表存在則洗掉,慎用會丟失資料
CREATE TABLE tb_user(
id INT,
NAME VARCHAR(30) UNIQUE NOT NULL,
phone VARCHAR(20) UNIQUE NOT NULL,
email VARCHAR(30) UNIQUE NOT NULL,
PRIMARY KEY (id)
);
DESC tb_user;
INSERT INTO tb_user (id,NAME) VALUES(1,'tony');
INSERT INTO tb_user (id,NAME) VALUES(2,'tony');
執行上面陳述句出錯:
Query : INSERT INTO tb_user (id,NAME) VALUES(2,'tony')
Error Code : 1062
Duplicate entry 'tony' for key 'name'
展示表結構:
DESC tb_user;
主鍵約束 primary key
主鍵是一條記錄的唯一標識,具有唯一性,不能重復
DROP TABLE IF EXISTS tb_user; #如果表存在則洗掉,慎用會丟失資料
CREATE TABLE tb_user(
id INT,
NAME VARCHAR(30),
PRIMARY KEY (id)
);
INSERT INTO tb_user (id,NAME) VALUES(1,'tony');
INSERT INTO tb_user (id,NAME) VALUES(1,'hellen');
第二句插入就會報錯:
Query : INSERT INTO tb_user (id,NAME) VALUES(1,'hellen')
Error Code : 1062
Duplicate entry '1' for key 'PRIMARY'
提示主鍵1的值已經存在,重復了
外鍵約束 foreign key
DROP TABLE IF EXISTS tb_user_address; #如果表存在則洗掉,慎用會丟失資料
DROP TABLE IF EXISTS tb_user; #如果表存在則洗掉,慎用會丟失資料
CREATE TABLE tb_user (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, #自增主鍵
NAME VARCHAR(50) NOT NULL UNIQUE, #非空,唯一索引
sex CHAR(2) DEFAULT '男', #默認值
phone CHAR(18),
age INT,
CHECK (age>0 AND age<=200),
);
CREATE TABLE tb_user_address (
user_id INT PRIMARY KEY NOT NULL,
address VARCHAR(200),
foreign key(user_id) REFERENCES tb_user(id)
);
DESC tb_user;
tb_user_address中user_id欄位錄入tb_user表不存在的主鍵值,將報錯
默認約束 default
默認值
DROP TABLE IF EXISTS tb_user; #如果表存在則洗掉,慎用會丟失資料
CREATE TABLE tb_user (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, #自增主鍵
NAME VARCHAR(50) NOT NULL UNIQUE, #非空,唯一索引
sex CHAR(2) DEFAULT '男', #默認值
phone CHAR(18),
age INT,
createdTime DATE DEFAULT NOW()
);
DESC tb_user;
檢查約束 check
很少使用,了解即可,錄入age超過200將報錯
DROP TABLE IF EXISTS tb_user; #如果表存在則洗掉,慎用會丟失資料
CREATE TABLE tb_user (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, #自增主鍵
NAME VARCHAR(50) NOT NULL UNIQUE, #非空,唯一索引
sex CHAR(2) DEFAULT '男', #默認值
phone CHAR(18),
age INT,
CHECK (age>0 AND age<=200),
createdTime DATE DEFAULT NOW()
);
DESC tb_user;
索引 index
定義
索引是一種排好序的快速查找的資料結構,它幫助資料庫高效的進行資料的檢索,在資料之外,資料庫系統還維護著滿足特定查找演算法的資料結構(額外的存盤空間),這些資料結構以某種方式指向資料,這樣就可以在這些資料結構上實作高效的查找演算法,這種資料結構就叫做索引,
一般來說索引本身也很大,不可能全部存盤在記憶體中,因此往往以索引檔案的形式存放在磁盤中,目前大多數索引都采用BTree樹方式構建,
分類
- 單值索引:一個索引只包括一個列,一個表可以有多個列
- 唯一索引:索引列的值必須唯一,但允許有空值;主鍵會自動創建唯一索引
- 復合索引:一個索引同時包括多列
創建索引
#查看索引,主鍵會自動創建索引
show index from dept;
#創建普通索引
#create index 索引名字 on 表名(欄位名); #創建索引
create index loc_index on dept(loc); #創建索引
# 創建唯一索引
#創建唯一索引--索引列的值必須唯一
CREATE UNIQUE INDEX 索引名 ON 表名(欄位名)
CREATE UNIQUE INDEX bindex ON dept(loc)
# 創建復合索引
#如果您希望索引不止一個列,您可以在括號中列出這些列的名稱,用逗號隔開:
CREATE INDEX 索引名 ON 表名 (欄位1, 欄位2)
CREATE INDEX PIndex ON Persons (LastName, FirstName)
# 洗掉索引
alter table dept drop index fuhe_index
索引掃描型別
type:
- ALL 全表掃描,沒有優化,最慢的方式
- index 索引全掃描,其次慢的方式
- range 索引范圍掃描,常用語<,<=,>=,between等操作
- ref 使用非唯一索引掃描或唯一索引前綴掃描,回傳單條記錄,常出現在關聯查詢中
- eq_ref 類似ref,區別在于使用的是唯一索引,使用主鍵的關聯查詢
- const/system 單條記錄,系統會把匹配行中的其他列作為常數處理,如主鍵或唯一索引查詢,system是const的特殊情況
- null MySQL不訪問任何表或索引,直接回傳結果
最左特性
explain
select * from dept where loc='二區' #使用了loc索引
explain
select * from dept where dname='研發部'#使用了dname索引
explain
select * from dept where dname='研發部' and loc='二區' #使用了dname索引
當我們創建一個聯合索引(復合索引)的時候,如(k1,k2,k3),相當于創建了(k1)、(k1,k2)和(k1,k2,k3)三個索引,這就是最左匹配原則,也稱為最左特性,
為何索引快?
明顯查詢索引表比直接查詢資料表要快的多,首先,索引表是排序了,可以類似二分查找,非常有效的提高了查詢的速度,
其程序如下圖,先到事先排序好的索引表中檢索查詢,找到其主鍵后,就直接定位到記錄所在位置,然后直接回傳這條資料,
- 排序,tree結構,類似二分查找
- 索引表小
小結
優點:
- 索引是資料庫優化
- 表的主鍵會默認自動創建索引
- 每個欄位都可以被索引
- 大量降低資料庫的IO磁盤讀寫成本,極大提高了檢索速度
- 索引事先對資料進行了排序,大大提高了查詢效率
缺點:
- 索引本身也是一張表,該表保存了主鍵與索引欄位,并指向物體表的記錄,所以索引列也要占用空間
- 索引表中的內容,在業務表中都有,資料是重復的,空間是“浪費的”
- 雖然索引大大提高了查詢的速度,但對資料的增、刪、改的操作需要更新索引表資訊,如果資料量非常巨大,更新效率就很慢,因為更新表時,MySQL不僅要保存資料,也要保存一下索引檔案
- 隨著業務的不斷變化,之前建立的索引可能不能滿足查詢需求,需要消耗我們的時間去更新索引
視圖View
概念
可視化的表,視圖當做是一個特殊的表,是指,把sql執行的結果,直接快取到了視圖中,
下次還要發起相同的sql,直接查視圖,現在用的少,了解即可.
使用: 1,創建視圖 2,使用視圖
測驗
create view 視圖名 as SQL陳述句;
select * from 視圖名;
#視圖:就是一個特殊的表,快取上次的查詢結果
#好處是提高了SQL的復用率,壞處是占記憶體無法被優化
#1.創建視圖
CREATE VIEW emp_view AS
SELECT * FROM emp WHERE ename LIKE '%a%' #模糊查詢,名字里包含a的
#2.使用視圖
SELECT * FROM emp_view
表關聯 association
概念
表table代表了生活中一個主體,如部門表dept,員工表emp,表關聯則代表了表之間的關系,如:部門和員工,商品和商品分類,老師和學生,教室和學生,
同時,也要知道,表并不都有關系,它們形成自己的小圈子,如商品和商品詳情一圈,部門和員工一圈,出圈就可能沒關系了,如商品和員工無關,商品和學生無關,
下面我們討論表的關系分為四種:
- 一對一 one to one QQ和QQ郵箱,員工和員工編號
- 一對多 one to many 最常見,部門和員工,用戶和訂單
- 多對一 many to one 一對多反過來,員工和部門,訂單和用戶
- 多對多 many to many 老師和學生,老師和課程
創建表
表設計特點:
- 表都以s結束,標識復數
- 欄位多以表的首字母作為開頭,在多表聯查時,方便標識出是哪個表的欄位
/*==============================================================*/
/* DBMS name: MySQL 5.0 */
/* Created on: 2020 */
/*==============================================================*/
drop table if exists courses;
drop table if exists scores;
drop table if exists students;
drop table if exists teachers;
/*==============================================================*/
/* Table: courses */
/*==============================================================*/
create table courses
(
cno varchar(5) not null,
cname varchar(10) not null,
tno varchar(3) not null,
primary key (cno)
);
/*==============================================================*/
/* Table: scores */
/*==============================================================*/
create table scores
(
sno varchar(3) not null,
cno varchar(5) not null,
degree numeric(10,1) not null,
primary key (sno, cno)
);
/*==============================================================*/
/* Table: students */
/*==============================================================*/
create table students
(
sno varchar(3) not null,
sname varchar(4) not null,
ssex varchar(2) not null,
sbirthday datetime,
class varchar(5),
primary key (sno)
);
/*==============================================================*/
/* Table: teachers */
/*==============================================================*/
create table teachers
(
tno varchar(3) not null,
tname varchar(4),
tsex varchar(2),
tbirthday datetime,
prof varchar(6),
depart varchar(10),
primary key (tno)
);
插入測驗資料
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (108 ,'曾華' ,'男' ,'1977-09-01',95033);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (105 ,'匡明' ,'男' ,'1975-10-02',95031);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (107 ,'王麗' ,'女' ,'1976-01-23',95033);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (101 ,'李軍' ,'男' ,'1976-02-20',95033);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (109 ,'王芳' ,'女' ,'1975-02-10',95031);
INSERT INTO STUDENTS (SNO,SNAME,SSEX,SBIRTHDAY,CLASS) VALUES (103 ,'陸君' ,'男' ,'1974-06-03',95031);
INSERT INTO TEACHERS(TNO,TNAME,TSEX,TBIRTHDAY,PROF,DEPART) VALUES (804,'易天','男','1958-12-02','副教授','計算機系');
INSERT INTO TEACHERS(TNO,TNAME,TSEX,TBIRTHDAY,PROF,DEPART) VALUES (856,'王旭','男','1969-03-12','講師','電子工程系');
INSERT INTO TEACHERS(TNO,TNAME,TSEX,TBIRTHDAY,PROF,DEPART) VALUES (825,'李萍','女','1972-05-05','助教','計算機系');
INSERT INTO TEACHERS(TNO,TNAME,TSEX,TBIRTHDAY,PROF,DEPART) VALUES (831,'陳冰','女','1977-08-14','助教','電子工程系');
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('3-105' ,'計算機導論',825);
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('3-245' ,'作業系統' ,804);
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('6-166' ,'模擬電路' ,856);
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('6-106' ,'概率論' ,831);
INSERT INTO COURSES(CNO,CNAME,TNO)VALUES ('9-888' ,'高等數學' ,831);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (103,'3-245',86);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (105,'3-245',75);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (109,'3-245',68);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (103,'3-105',92);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (105,'3-105',88);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (109,'3-105',76);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (101,'3-105',64);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (107,'3-105',91);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (108,'3-105',78);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (101,'6-166',85);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (107,'6-106',79);
INSERT INTO SCORES(SNO,CNO,DEGREE)VALUES (108,'6-166',81);
多表聯查 join
笛卡爾積 Cartesian product
多表查詢是指基于兩個和兩個以上的表的查詢,在實際應用中,查詢單個表可能不能滿足你的需求,如顯示員工表emp中不只顯示deptno,還要顯示部門名稱,而部門名稱dname在dept表中,
#把兩個表的資料都拼接起來
SELECT * FROM dept,emp
上面這種查詢兩個表的方式稱為:笛卡爾積(Cartesian product),又稱直積,一般笛卡爾積沒有實際的業務意義,但多表查詢都是先生成笛卡爾積,再進行資料的篩選過濾,
這點很值得注意,實際開發中盡量少用多表聯查,其根本原因就在這里,查詢程序中,現在記憶體中構建一個大大的結果集,然后再進行資料的過濾,那這個構建程序,和所使用的記憶體資源,包括過濾時的判斷,都是既耗費資源,又浪費時間,
這就是阿里規范中禁止3張表以上的聯查的原因:
三種連接 join
- 內連接 inner join
- 左(外)連接 left join
- 右(外)連接 right join
案例:列出research部門下的所有員工的資訊
SELECT * FROM emp
WHERE deptno = ( SELECT deptno FROM dept WHERE dname='research' )
案例:怎么用內鏈接 INNER JOIN 實作上面的需求?
SELECT d.dname,e.ename,e.job
FROM emp e INNER JOIN dept d
ON e.deptno=d.deptno
WHERE d.dname='research'
換成left join和right join,看看有什么不同呢?
案例:列出tony的擴展資訊
SELECT *
FROM emp e INNER JOIN empext t
ON e.empno=t.empno
WHERE e.ename='tony'
換成left join和right join,看看有什么不同呢?
inner join、left join、right join的區別?

- INNER JOIN兩邊都對應有記錄的才展示,其他去掉
- LEFT JOIN左邊表中的資料都出現,右邊沒有資料以NULL填充
- RIGHT JOIN右邊表中的資料都出現,左邊沒有資料以NULL填充
子查詢 subquery
概念
子查詢是指嵌入在其他select陳述句中的select陳述句,也叫嵌套查詢,子查詢執行效率低慎用,記錄少時效率影響不大、圖方便直接使用,記錄多時最好使用其它方式替代,
單行子查詢 =
回傳結果為一個
--列出tony所在部門的所有人員
select deptno from emp where ename='tony';
select * from emp where deptno = (select deptno from emp where ename='tony');
多行子查詢 in
in子查詢
select * from emp where job in ('經理','員工');
select * from emp where job in (select distinct job from emp);
SQL面試題
查詢所有記錄
select * from emp
只查詢指定列
SELECT id,ename,sal from emp
查詢id為100的記錄
select * from emp where id=100
模糊查詢記錄
select * from emp where ename like 'j%' #以j開頭的記錄
select * from emp where ename like '%k' #以k結束的記錄
select * from emp where ename like '%a%' #包含a的記錄
select * from emp where ename not like 'j%' #不 以j開頭的記錄
查詢之間范圍之間的所有記錄
select * from emp where sal between 8000 and 20000 #[8000,20000]
select * from emp where sal>8000 and sal<20000 #(8000,20000)
查詢滿足兩個條件的記錄
SELECT * from user where age=19 or age=20 #或者關系
SELECT * from user where age in (19,20)
SELECT * from user where age=20 and name='xiongda' #并且關系
查詢用戶住址
SELECT distinct addr from user
查詢19歲人的名字
SELECT distinct name from user where age=19
按age升序查詢記錄
SELECT * from user order by age asc #升序,默認
SELECT * from user order by age desc #降序
以name升序、age降序查詢記錄
SELECT * from user order by name asc,age desc #name升序,age降序
查詢總人數
SELECT count(*) from user
SELECT count(1) from user
SELECT count(id) from user
查詢各個城市的人數
select addr,count(addr) from user group by addr #聚合函式以外的列要分組
查詢至少有2人的地址
SELECT addr,count(name) from user GROUP BY addr
SELECT addr,count(name) X from user GROUP BY addr having X>2 #條件過濾
查詢記錄中最年長和最年輕
select max(age),min(age) from user
查詢大于平均年齡的記錄
select * from user where age > (select avg(age) from user)
查詢年齡最大的用戶資訊
select * from user where age = (select max(age) from user)
查詢各部門的最高薪
select id,name,sal,max(sal) from emp GROUP BY deptno
查詢各科的平均工資
select avg(comm) from emp
select ROUND(avg(comm),1) from emp #保留一位小數
SELECT * from emp where comm > (select avg(comm) from emp)
查詢id是100或200的記錄
select * from emp where id=100
select * from emp where id=200
select * from emp where id=100 or id=200
select * from emp where id in(100,200)
select * from emp where id=200
#UNION #合并重復內容
union all #不合并重復內容
select * from emp where id=200
查詢存在部門的員工資訊
select * from emp where deptno in (select id from dept)
查詢沒劃分部門的員工資訊
select * from emp where deptno not in(select id from dept)
查詢同名的員工記錄
select * from emp WHERE ename in (
select ename from emp GROUP BY ename HAVING count(ename)>1
)
全部學生按出生年月排行
select * from students order by sbirthday #數值從小到大,年齡就是大到小了
每個班上最小年齡的學員
select sname,class,max(sbirthday) from students group by class #數字最大,年齡是最小的
查詢學生的姓名和年齡
select sname,year(now())-year(sbirthday) age from students
查詢男教師及其所上的課程
SELECT * from teachers a inner JOIN courses b on a.tno=b.tno AND a.tsex='男'
SELECT * from teachers a,courses b where a.tno=b.tno AND a.tsex='男'
查詢每個老師教的課程
SELECT c.cname,t.tname,t.prof,t.depart
FROM teachers t
LEFT JOIN courses c ON t.tno = c.tno
查詢女老師的資訊
SELECT *
FROM teachers t
LEFT JOIN courses c ON t.tno = c.tno
where t.tsex='女'
第一種先連接資料后過濾資料,假如資料量很大,第一種中間程序要構建巨大的臨時表,而第二種方式先過濾資料,構建的中間結果集自然就變的很小,所占記憶體,所加工的時間所網路傳輸的時間都變少了,所以效率高,
查詢得分前3名的學員資訊
select * from scores order by degree desc limit 3 #前三條
select * from scores order by degree desc limit 1,3
#從1位置(第二條)開始,總共取3條
查詢課程是“計算機導論”的,得分前3名的學員資訊
select * from scores where cno = (select cno from courses where cname='計算機導論')
order by degree desc limit 3
課程號“3-105”的倒數最后3名學員排行
select * from scores where cno='3-105' order by degree limit 3
SQL的執行順序
(1) FROM [left_table] 選擇表
(2) ON <join_condition> 鏈接條件
(3) <join_type> JOIN <right_table> 鏈接
(4) WHERE <where_condition> 條件過濾
(5) GROUP BY <group_by_list> 分組
(6) AGG_FUNC(column or expression),... 聚合
(7) HAVING <having_condition> 分組過濾
(8) SELECT (9) DISTINCT column,... 選擇欄位、去重
(9) ORDER BY <order_by_list> 排序
(10) LIMIT count OFFSET count; 分頁
SQL優化
創建mysql-db庫
CREATE DATABASE /*!32312 IF NOT EXISTS*/`mysql-db` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `mysql-db`;
準備student表
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` varchar(4) NOT NULL,
`NAME` varchar(20) DEFAULT NULL,
`sex` char(2) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`salary` decimal(7,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `student`(`id`,`NAME`,`sex`,`birthday`,`salary`) values ('1','張慎政','男','2020-01-01','10000.00'),('2','劉沛霞','女','2020-01-02','10000.00'),('3','劉昱江','男','2020-01-03','10000.00'),('4','齊雷','男','2020-01-04','20000.00'),('5','王海濤','男','2020-01-05','20000.00'),('6','董長春','男','2020-01-06','10000.00'),('7','張久軍','男','2020-01-07','20000.00'),('8','陳子樞','男','2020-10-11','3000.00');
準備tb_dept表
DROP TABLE IF EXISTS `tb_dept`;
CREATE TABLE `tb_dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`parent_id` int(11) DEFAULT NULL,
`sort` int(11) DEFAULT NULL,
`note` varchar(100) DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
insert into `tb_dept`(`id`,`name`,`parent_id`,`sort`,`note`,`created`,`updated`) values (1,'集團',0,1,'集團總部','2018-10-02 09:15:14','2018-09-27 16:35:54'),(2,'財務部',1,2,'財務管理','2018-09-27 16:35:52','2018-09-27 16:34:15'),(3,'軟體部',1,3,'開發軟體、運維','2018-09-27 16:35:54','2018-09-27 16:34:51');
準備tb_user表
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dept_id` int(11) DEFAULT NULL,
`username` varchar(50) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`salt` varchar(50) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`mobile` varchar(100) DEFAULT NULL,
`valid` tinyint(4) DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
insert into `tb_user`(`id`,`dept_id`,`username`,`password`,`salt`,`email`,`mobile`,`valid`,`created`,`updated`) values (1,1,'陳集團','123456',NULL,'tony@sina.com','13572801415',1,'2018-09-30 09:32:18','2018-09-30 09:32:18'),(2,3,'牛軟體','567890',NULL,'niu@sina.com','13208737172',0,'2018-10-02 09:23:19','2018-09-20 09:32:18');
查詢SQL盡量不要使用select *,而是具體欄位
反例:SELECT * FROM student
正例:SELECT id,NAME FROM student
理由:
欄位多時,大表能達到100多個欄位甚至達200多個欄位
只取需要的欄位,節省資源、減少網路開銷
select * 進行查詢時,很可能不會用到索引,就會造成全表掃描
避免在where子句中使用or來連接條件
反例:SELECT * FROM student WHERE id=1 OR salary=30000
正例:
# 分開兩條sql寫
SELECT * FROM student WHERE id=1
SELECT * FROM student WHERE salary=30000
理由:
使用or可能會使索引失效,從而全表掃描
對于or沒有索引的salary這種情況,假設它走了id的索引,但是走到salary查詢條件時,它還得全表掃描,也就是說整個程序需要三步:全表掃描+索引掃描+合并,如果它一開始就走全表掃描,直接一遍掃描就搞定,雖然mysql是有優化器的,處于效率與成本考慮,遇到or條件,索引還是可能失效的
使用varchar代替char
反例:`deptname` char(100) DEFAULT NULL COMMENT '部門名稱'
正例:`deptname` varchar(100) DEFAULT NULL COMMENT '部門名稱'
理由:
varchar變長欄位按資料內容實際長度存盤,存盤空間小,可以節省存盤空間
char按宣告大小存盤,不足補空格
其次對于查詢來說,在一個相對較小的欄位內搜索,效率更高
盡量使用數值替代字串型別
主鍵(id):primary key優先使用數值型別int,tinyint
性別(sex):0-代表女,1-代表男;資料庫沒有布爾型別,mysql推薦使用tinyint
支付方式(payment):1-現金、2-微信、3-支付寶、4-信用卡、5-銀行卡
服務狀態(state):1-開啟、2-暫停、3-停止
商品狀態(state):1-上架、2-下架、3-洗掉
查詢盡量避免回傳大量資料
如果查詢回傳資料量很大,就會造成查詢時間過長,網路傳輸時間過長,同時,大量資料回傳也可能沒有實際意義,如回傳上千條甚至更多,用戶也看不過來,
通常采用分頁,一頁習慣10/20/50/100條,
使用explain分析你SQL執行計劃
SQL很靈活,一個需求可以很多實作,那哪個最優呢?SQL提供了explain關鍵字,它可以分析你的SQL執行計劃,看它是否最佳,Explain主要看SQL是否使用了索引,
EXPLAIN
SELECT * FROM student WHERE id=1
是否使用了索引及其掃描型別
type:
- ALL 全表掃描,沒有優化,最慢的方式
- index 索引全掃描
- range 索引范圍掃描,常用語<,<=,>=,between等操作
- ref 使用非唯一索引掃描或唯一索引前綴掃描,回傳單條記錄,常出現在關聯查詢中
- eq_ref 類似ref,區別在于使用的是唯一索引,使用主鍵的關聯查詢
- const/system 單條記錄,系統會把匹配行中的其他列作為常數處理,如主鍵或唯一索引查詢
- null MySQL不訪問任何表或索引,直接回傳結果
key:
- 真正使用的索引方式
創建name欄位的索引
ALTER TABLE student ADD INDEX index_name (NAME)
優化like陳述句
模糊查詢,程式員最喜歡的就是使用like,但是like很可能讓你的索引失效
反例:
EXPLAIN
SELECT id,NAME FROM student WHERE NAME LIKE '%1'
EXPLAIN
SELECT id,NAME FROM student WHERE NAME LIKE '%1%'
正例:
EXPLAIN
SELECT id,NAME FROM student WHERE NAME LIKE '1%'
字串怪現象
反例:
#未使用索引
EXPLAIN
SELECT * FROM student WHERE NAME=123
正例:
#使用索引
EXPLAIN
SELECT * FROM student WHERE NAME='123'
理由:
- 為什么第一條陳述句未加單引號就不走索引了呢?這是因為不加單引號時,是字串跟數字的比較,它們型別不匹配,MySQL會做隱式的型別轉換,把它們轉換為數值型別再做比較
索引不宜太多,一般5個以內
- 索引并不是越多越好,雖其提高了查詢的效率,但卻會降低插入和更新的效率
- 索引可以理解為一個就是一張表,其可以存盤資料,其資料就要占空間
- 再者,索引表的一個特點,其資料是排序的,那排序要不要花時間呢?肯定要
- insert或update時有可能會重建索引,如果資料量巨大,重建將進行記錄的重新排序,所以建索引需要慎重考慮,視具體情況來定
- 一個表的索引數最好不要超過5個,若太多需要考慮一些索引是否有存在的必要
索引不適合建在有大量重復資料的欄位上
如性別欄位,因為SQL優化器是根據表中資料量來進行查詢優化的,如果索引列有大量重復資料,Mysql查詢優化器推算發現不走索引的成本更低,很可能就放棄索引了,
where限定查詢的資料
資料中假定就一個男的記錄
反例:
SELECT id,NAME FROM student WHERE sex='男'
正例:
SELECT id,NAME FROM student WHERE id=1 AND sex='男'
理由:
- 需要什么資料,就去查什么資料,避免回傳不必要的資料,節省開銷
避免在where中對欄位進行運算式操作
反例:
EXPLAIN
SELECT * FROM student WHERE id+1-1=+1
正例:
EXPLAIN
SELECT * FROM student WHERE id=+1-1+1
EXPLAIN
SELECT * FROM student WHERE id=1
理由:
- SQL決議時,如果欄位相關的是運算式就進行全表掃描
-
避免在where子句中使用!=或<>運算子
應盡量避免在where子句中使用!=或<>運算子,否則引擎將放棄使用索引而進行全表掃描,記住實作業務優先,實在沒辦法,就只能使用,并不是不能使用,如果不能使用,SQL也就無需支持了,
反例:
EXPLAIN
SELECT * FROM student WHERE salary!=3000
EXPLAIN
SELECT * FROM student WHERE salary<>3000
理由:
- 使用!=和<>很可能會讓索引失效
-
去重distinct過濾欄位要少
#索引失效
EXPLAIN
SELECT DISTINCT * FROM student
#索引生效
EXPLAIN
SELECT DISTINCT id,NAME FROM student
EXPLAIN
SELECT DISTINCT NAME FROM student
理由:
- 帶distinct的陳述句占用cpu時間高于不帶distinct的陳述句,因為當查詢很多欄位時,如果使用distinct,資料庫引擎就會對資料進行比較,過濾掉重復資料,然而這個比較、過濾的程序會占用系統資源,如cpu時間
where中使用默認值代替null
#修改表,增加age欄位,型別int,非空,默認值0
ALTER TABLE student ADD age INT NOT NULL DEFAULT 0;
批量插入性能提升
大量資料提交,上千,上萬,批量性能非常快,mysql獨有
多條提交:
INSERT INTO student (id,NAME) VALUES(4,'齊雷');
INSERT INTO student (id,NAME) VALUES(5,'劉昱江');
批量提交:
INSERT INTO student (id,NAME) VALUES(4,'齊雷'),(5,'劉昱江');
理由:
- 默認新增SQL有事務控制,導致每條都需要事務開啟和事務提交;而批量處理是一次事務開啟和提交,自然速度飛升
- 資料量小體現不出來
批量洗掉優化
避免同時修改或洗掉過多資料,因為會造成cpu利用率過高,會造成鎖表操作,從而影響別人對資料庫的訪問,
反例:
#一次洗掉10萬或者100萬+?
delete from student where id <100000;
#采用單一回圈操作,效率低,時間漫長
for(User user:list){
delete from student;
}
正例:
//分批進行洗掉,如每次500
for(){
delete student where id<500;
}
delete student where id>=500 and id<1000;
理由:
- 一次性洗掉太多資料,可能造成鎖表,會有lock wait timeout exceed的錯誤,所以建議分批操作
偽洗掉設計
商品狀態(state):1-上架、2-下架、3-洗掉
理由:
- 這里的洗掉只是一個標識,并沒有從資料庫表中真正洗掉,可以作為歷史記錄備查
- 同時,一個大型系統中,表關系是非常復雜的,如電商系統中,商品作廢了,但如果直接洗掉商品,其它商品詳情,物流資訊中可能都有其參考,
- 通過where state=1或者where state=2過濾掉資料,這樣偽洗掉的資料用戶就看不到了,從而不影響用戶的使用
- 操作速度快,特別資料量很大情況下
提高group by陳述句的效率
可以在執行到該陳述句前,把不需要的記錄過濾掉
反例:先分組,再過濾
select job,avg(salary) from employee
group by job
having job ='president' or job = 'managent';
正例:先過濾,后分組
select job,avg(salary) from employee
where job ='president' or job = 'managent'
group by job;
復合索引最左特性
創建復合索引,也就是多個欄位
ALTER TABLE student ADD INDEX idx_name_salary (NAME,salary)
滿足復合索引的左側順序,哪怕只是部分,復合索引生效
EXPLAIN
SELECT * FROM student WHERE NAME='陳子樞'
沒有出現左邊的欄位,則不滿足最左特性,索引失效
EXPLAIN
SELECT * FROM student WHERE salary=3000
復合索引全使用,按左側順序出現 name,salary,索引生效
EXPLAIN
SELECT * FROM student WHERE NAME='陳子樞' AND salary=3000
雖然違背了最左特性,但MYSQL執行SQL時會進行優化,底層進行顛倒優化
EXPLAIN
SELECT * FROM student WHERE salary=3000 AND NAME='陳子樞'
理由:
- 復合索引也稱為聯合索引
- 當我們創建一個聯合索引的時候,如(k1,k2,k3),相當于創建了(k1)、(k1,k2)和(k1,k2,k3)三個索引,這就是最左匹配原則
- 聯合索引不滿足最左原則,索引一般會失效,但是這個還跟Mysql優化器有關的
排序欄位創建索引
什么樣的欄位才需要創建索引呢?原則就是where和order by中常出現的欄位就創建索引,
#使用*,包含了未索引的欄位,導致索引失效
EXPLAIN
SELECT * FROM student ORDER BY NAME;
EXPLAIN
SELECT * FROM student ORDER BY NAME,salary
#name欄位有索引
EXPLAIN
SELECT id,NAME FROM student ORDER BY NAME
#name和salary復合索引
EXPLAIN
SELECT id,NAME FROM student ORDER BY NAME,salary
EXPLAIN
SELECT id,NAME FROM student ORDER BY salary,NAME
#排序欄位未創建索引,性能就慢
EXPLAIN
SELECT id,NAME FROM student ORDER BY sex
洗掉冗余和重復的索引
SHOW INDEX FROM student
#創建索引index_name
ALTER TABLE student ADD INDEX index_name (NAME)
#洗掉student表的index_name索引
DROP INDEX index_name ON student ;
#修改表結果,洗掉student表的index_name索引
ALTER TABLE student DROP INDEX index_name ;
#主鍵會自動創建索引,洗掉主鍵索引
ALTER TABLE student DROP PRIMARY KEY ;
不要有超過5個以上的表連接
- 關聯的表個數越多,編譯的時間和開銷也就越大
- 每次關聯記憶體中都生成一個臨時表
- 應該把連接表拆開成較小的幾個執行,可讀性更高
- 如果一定需要連接很多表才能得到資料,那么意味著這是個糟糕的設計了
- 阿里規范中,建議多表聯查三張表以下
inner join 、left join、right join,優先使用inner join
三種連接如果結果相同,優先使用inner join,如果使用left join左邊表盡量小
- inner join 內連接,只保留兩張表中完全匹配的結果集
- left join會回傳左表所有的行,即使在右表中沒有匹配的記錄
- right join會回傳右表所有的行,即使在左表中沒有匹配的記錄
理由:
- 如果inner join是等值連接,回傳的行數比較少,所以性能相對會好一點
- 同理,使用了左連接,左邊表資料結果盡量小,條件盡量放到左邊處理,意味著回傳的行數可能比較少,這是mysql優化原則,就是小表驅動大表,小的資料集驅動大的資料集,從而讓性能更優
in子查詢的優化
日常開發實作業務需求可以有兩種方式實作:
- 一種使用資料庫SQL腳本實作
- 一種使用程式實作
如需求:查詢所有部門的所有員工:
#in子查詢
SELECT * FROM tb_user WHERE dept_id IN (SELECT id FROM tb_dept);
#這樣寫等價于:
#先查詢部門表
SELECT id FROM tb_dept
#再由部門dept_id,查詢tb_user的員工
SELECT * FROM tb_user u,tb_dept d WHERE u.dept_id = d.id
假設表A表示某企業的員工表,表B表示部門表,查詢所有部門的所有員工,很容易有以下程式實作,可以抽象成這樣的一個嵌套回圈:
List<> resultSet;
for(int i=0;i<B.length;i++) {
for(int j=0;j<A.length;j++) {
if(A[i].id==B[j].id) {
resultSet.add(A[i]);
break;
}
}
}
上面的需求使用SQL就遠不如程式實作,特別當資料量巨大時,
理由:
- 資料庫最費勁的就是程式鏈接的釋放,假設鏈接了兩次,每次做上百萬次的資料集查詢,查完就結束,這樣就只做了兩次;相反建立了上百萬次鏈接,申請鏈接釋放反復重復,就會額外花費很多實際,這樣系統就受不了了,慢,卡頓
盡量使用union all替代union
反例:
SELECT * FROM student
UNION
SELECT * FROM student
正例:
SELECT * FROM student
UNION ALL
SELECT * FROM student
理由:
- union和union all的區別是,union會自動去掉多個結果集合中的重復結果,而union all則將所有的結果全部顯示出來,不管是不是重復
- union:對兩個結果集進行并集操作,不包括重復行,同時進行默認規則的排序
- union在進行表鏈接后會篩選掉重復的記錄,所以在表鏈接后會對所產生的結果集進行排序運算,洗掉重復的記錄再回傳結果,實際大部分應用中是不會產生重復的記錄,最常見的是程序表與歷史表UNION
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/345690.html
標籤:其他
