MySQL多表查詢與事務
學習目標
- 能夠使用內連線進行多表查詢
- 能夠使用左外連線和右外連線進行多表查詢
- 能夠使用子查詢進行多表查詢
- 能夠理解多表查詢的規律
- 能夠理解事務的概念
- 能夠說出事務的原理
- 能夠在MySQL中使用事務
- 能夠理解髒讀,不可重複讀,幻讀的概念及解決辦法
第1章 多表查詢
1.1 什麼是多表查詢
同時查詢多張表獲取到需要的資料
比如:我們想查詢到開發部有多少人,需要將部門表和員工表同時進行查詢
多表查詢的分類:
準備資料:
-- 建立部門表 CREATE TABLE dept ( id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20) ); INSERT INTO dept (NAME) VALUES ('開發部'),('市場部'),('財務部'); -- 建立員工表 CREATE TABLE emp ( id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(10),gender CHAR(1),-- 性別 salary DOUBLE,-- 工資 join_date DATE,-- 入職日期 dept_id INT ); INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('孫悟空','男',7200,'2013-02-24',1); INSERT INTO emp(NAME,dept_id) VALUES('豬八戒',3600,'2010-12-02',2); INSERT INTO emp(NAME,dept_id) VALUES('唐僧',9000,'2008-08-08',dept_id) VALUES('白骨精','女',5000,'2015-10-07',3); INSERT INTO emp(NAME,dept_id) VALUES('蜘蛛精',4500,'2011-03-14',1);
1.2 笛卡爾積現象
1.2.1 什麼是笛卡爾積現象
多表查詢時左表的每條資料和右表的每條資料組合,這種效果成為笛卡爾積
需求:查詢每個部門有哪些人
具體操作:
SELECT * FROM dept,emp;
以上資料其實是左表的每條資料和右表的每條資料組合。左表有3條,右表有5條,最終組合後3*5=15條資料。
左表的每條資料和右表的每條資料組合,這種效果稱為笛卡爾乘積
1.2.2 如何清除笛卡爾積現象的影響
我們發現不是所有的資料組合都是有用的,只有員工表.dept_id = 部門表.id 的資料才是有用的。所以需要通過條件過濾掉沒用的資料。
SELECT * FROM dept,emp WHERE emp.`dept_id`=dept.`id`;
1.3 內連線
用左邊表的記錄去匹配右邊表的記錄,如果符合條件的則顯示
1.3.1 隱式內連線
隱式內連線:看不到JOIN
關鍵字,條件使用WHERE
指定
SELECT 欄位名 FROM 左表,右表 WHERE 條件;
1.3.2 顯示內連線
顯示內連線:使用INNER JOIN ... ON
語句,可以省略INNER
SELECT 欄位名 FROM 左表 INNER JOIN 右表 ON 條件;
具體操作:
- 查詢唐僧的資訊,顯示員工id,姓名,性別,工資和所在的部門名稱,我們發現需要聯合2張表同時才能查詢出需要的資料,我們使用內連線
- 確定查詢哪些表
SELECT * FROM dept INNER JOIN emp;
- 確定表連線條件,員工表.dept_id = 部門表.id 的資料才是有效的
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id`;
- 確定表連線條件,我們查詢的是唐僧的資訊,部門表.name=‘唐僧’
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id` AND emp.`NAME`='唐僧';
- 確定查詢欄位,查詢唐僧的資訊,顯示員工id,姓名,性別,工資和所在的部門名稱
SELECT emp.`id`,emp.`NAME`,emp.`gender`,emp.`salary`,dept.`NAME` FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id` AND emp.`NAME`='唐僧';
- 我們發現寫表名有點長,可以給表取別名,顯示的欄位名也使用別名
SELECT e.`id` 員工編號,e.`NAME` 員工姓名,e.`gender` 性別,e.`salary` 工資,d.`NAME` 部門名稱 FROM dept d INNER JOIN emp e ON e.`dept_id`=d.`id` AND e.`NAME`='唐僧';
總結內連線查詢步驟:
- 確定查詢哪些表
- 確定表連線條件
- 確定查詢欄位
1.4 左外連線
左外連線:使用LEFT OUTER JOIN ... ON
,OUTER
可以省略
SELECT 欄位名 FROM 左表 LEFT OUTER JOIN 右表 ON 條件;
用左邊表的記錄去匹配右邊表的記錄,如果符合條件的則顯示;否則,顯示NULL
可以理解為:在內連線的基礎上保證左表的資料全部顯示
具體操作:
- 在部門表中增加一個銷售部
INSERT INTO dept (NAME) VALUES ('銷售部');
- 使用內連線查詢
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id`;
- 使用左外連線查詢
SELECT * FROM dept LEFT OUTER JOIN emp ON emp.`dept_id`=dept.`id`;
1.5 右外連線
右外連線:使用RIGHT OUTER JOIN ... ON
,OUTER
可以省略
SELECT 欄位名 FROM 左表 RIGHT OUTER JOIN 右表 ON 條件;
用右邊表的記錄去匹配左邊表的記錄,如果符合條件的則顯示;否則,顯示NULL
可以理解為:在內連線的基礎上保證右表的資料全部顯示
具體操作:
- 在員工表中增加一個員工
INSERT INTO emp(NAME,dept_id) VALUES('沙僧',6666,NULL);
- 使用內連線查詢
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id`;
- 使用右外連線查詢
SELECT * FROM dept RIGHT OUTER JOIN emp ON emp.`dept_id`=dept.`id`;
1.6 子查詢
一條SELECT語句結果作為另一條SELECT語法一部分(查詢條件,查詢結果,表)
SELECT 查詢欄位 FROM 表 WHERE 查詢條件;
SELECT * FROM employee WHERE salary=(SELECT MAX(salary) FROM employee);
子查詢需要放在()中
子查詢結果的三種情況:
- 子查詢的結果是一個值的時候
- 子查詢結果是單例多行的時候
- 子查詢的結果是多行多列
說明:
子查詢結果只要是單列
,肯定在WHERE
後面作為條件
子查詢結果只要是多列
,肯定在FROM
後面作為表
1.6.1 子查詢的結果是一個值的時候
子查詢結果只要是單列
,肯定在WHERE
後面作為條件
SELECT 查詢欄位 FROM 表 WHERE 欄位=(子查詢);
-
查詢工資最高的員工是誰?
- 查詢最高工資是多少
SELECT MAX(salary) FROM emp;
- 根據最高工資到員工表查詢到對應的員工資訊
SELECT * FROM emp WHERE salary=(SELECT MAX(salary) FROM emp);
-
查詢工資小於平均工資的員工有哪些?
- 查詢平均工資是多少
SELECT AVG(salary) FROM emp;
- 到員工表查詢小於平均的員工資訊
SELECT * FROM emp WHERE salary < (SELECT AVG(salary) FROM emp);
1.6.2 子查詢結果是單例多行的時候
子查詢結果只要是單列
,肯定在WHERE
後面作為條件
子查詢結果是單例多行,結果集類似於一個數組,父查詢使用IN
運算子
SELECT 查詢欄位 FROM 表 WHERE 欄位 IN (子查詢);
-
查詢工資大於5000的員工,來自於哪些部門的名字
- 先查詢大於5000的員工所在的部門id
SELECT dept_id FROM emp WHERE salary > 5000;
- 再查詢在這些部門id中部門的名字
SELECT dept.name FROM dept WHERE dept.id IN (SELECT dept_id FROM emp WHERE salary > 5000);
-
查詢開發部與財務部所有的員工資訊
- 先查詢開發部與財務部的id
SELECT id FROM dept WHERE NAME IN('開發部','財務部');
- 再查詢在這些部門id中有哪些員工
SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE NAME IN('開發部','財務部'));
1.6.3 子查詢的結果是多行多列
子查詢結果只要是多列
,肯定在FROM
後面作為表
SELECT 查詢欄位 FROM (子查詢) 表別名 WHERE 條件;
子查詢作為表需要取別名,否則這張表沒用名稱無法訪問表中的欄位
-
查詢出2011年以後入職的員工資訊,包括部門名稱
- 在員工表中查詢2011-1-1以後入職的員工
SELECT * FROM emp WHERE join_date > '2011-1-1';
- 查詢所有的部門資訊,與上面的虛擬表中的資訊組合,找出所有部門id等於的dept_id
SELECT * FROM dept d,(SELECT * FROM emp WHERE join_date > '2011-1-1') e WHERE e.dept_id = d.id;
使用表連線:
SELECT d.*,e.* FROM dept d INNER JOIN emp e ON d.id = e.dept_id WHERE e.join_date > '2011-1-1';
1.6.4 多表查詢總結
- 子查詢結果只要是
單列
,肯定在WHERE
後面作為條件
SELECT 查詢欄位 FROM 表 WHERE 欄位=(子查詢);
- 子查詢結果只要是
多列
,肯定在FROM
後面作為表
SELECT 查詢欄位 FROM (子查詢) 表別名 WHERE 條件;
第2章 多表查詢案例
我們在公司開發中,根據不同的業務需求往往需要通過2張及以上的表中去查詢需要的資料。所以我們有必要學習2張及以上的表的查詢。其實不管是幾張表的查詢,都是有規律可循的。
2.1 準備資料
-- 部門表
CREATE TABLE dept (
id INT PRIMARY KEY PRIMARY KEY,-- 部門id
dname VARCHAR(50),-- 部門名稱
loc VARCHAR(50) -- 部門位置
);
-- 新增4個部門
INSERT INTO dept(id,dname,loc) VALUES
(10,'教研部','北京'),(20,'學工部','上海'),(30,'銷售部','廣州'),(40,'財務部','深圳');
-- 職務表,職務名稱,職務描述
CREATE TABLE job (
id INT PRIMARY KEY,jname VARCHAR(20),description VARCHAR(50)
);
-- 新增4個職務
INSERT INTO job (id,jname,description) VALUES
(1,'董事長','管理整個公司,接單'),(2,'經理','管理部門員工'),(3,'銷售員','向客人推銷產品'),(4,'文員','使用辦公軟體');
-- 員工表
CREATE TABLE emp (
id INT PRIMARY KEY,-- 員工id
ename VARCHAR(50),-- 員工姓名
job_id INT,-- 職務id
mgr INT,-- 上級領導
joindate DATE,-- 入職日期
salary DECIMAL(7,2),-- 工資
bonus DECIMAL(7,-- 獎金
dept_id INT,-- 所在部門編號
CONSTRAINT emp_jobid_ref_job_id_fk FOREIGN KEY (job_id) REFERENCES job (id),CONSTRAINT emp_deptid_ref_dept_id_fk FOREIGN KEY (dept_id) REFERENCES dept (id)
);
-- 新增員工
INSERT INTO emp(id,ename,job_id,mgr,joindate,bonus,dept_id) VALUES
(1001,'孫悟空',4,1004,'2000-12-17','8000.00',NULL,20),(1002,'盧俊義',3,1006,'2001-02-20','16000.00','3000.00',30),(1003,'林沖','2001-02-22','12500.00','5000.00',(1004,'唐僧',2,1009,'2001-04-02','29750.00',(1005,'李逵','2001-09-28','14000.00',(1006,'宋江','2001-05-01','28500.00',(1007,'劉備','2001-09-01','24500.00',10),(1008,'豬八戒','2007-04-19','30000.00',(1009,'羅貫中',1,'2001-11-17','50000.00',(1010,'吳用','2001-09-08','15000.00','0.00',(1011,'沙僧','2007-05-23','11000.00',(1012,'2001-12-03','9500.00',(1013,'小白龍',(1014,'關羽',1007,'2002-01-23','13000.00',10);
-- 工資等級表
CREATE TABLE salarygrade (
grade INT PRIMARY KEY,losalary INT,hisalary INT
);
-- 新增5個工資等級
INSERT INTO salarygrade(grade,losalary,hisalary) VALUES
(1,7000,12000),12010,14000),14010,20000),20010,30000),(5,30010,99990);
分析4張表的關係:通過4張表可以查出一個員工的所有資訊
2.2 練習
2.2.1 練習1
查詢所有員工資訊。顯示員工編號,員工姓名,工資,職務名稱,職務描述
具體操作:
1.確定要查詢哪些表:emp e,job j
SELECT * FROM emp e INNER JOIN job j;
2.確定表連線條件: e.job_id=j.id
SELECT * FROM emp e INNER JOIN job j ON e.job_id=j.id;
3.確定查詢欄位:員工編號,員工姓名,工資,職務名稱,職務描述
SELECT e.`id`,e.`ename`,e.`salary`,j.`jname`,j.`description` FROM emp e INNER JOIN job j ON e.job_id=j.id;
2.2.2 練習2
查詢所有員工資訊。顯示員工編號,員工姓名,工資,職務名稱,職務描述,部門名稱,部門位置
具體操作:
1. 確定要查詢哪些表,emp e,job j,dept d
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d;
2. 確定表連線條件 e.job_id=j.id and e.dept_id=d.id
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d ON e.job_id=j.id AND e.dept_id=d.id;
3. 確定查詢欄位:員工編號,員工姓名,工資,職務名稱,職務描述,部門名稱,部門位置
SELECT e.`id`,j.`description`,d.`dname`,d.`loc` FROM emp e INNER JOIN job j INNER JOIN dept d ON e.job_id=j.id AND e.dept_id=d.id;
2.2.3 練習3
查詢所有員工資訊。顯示員工姓名,工資,職務名稱,職務描述,部門名稱,部門位置,工資等級
具體操作:
1. 確定要查詢哪些表,emp e,dept d,salarygrade s
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s;
2. 確定表連線條件 e.job_id=j.id and e.dept_id=d.id and e.salary between s.losalary and hisalary
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s ON e.job_id=j.id AND e.dept_id=d.id AND e.salary BETWEEN s.losalary AND hisalary;
3. 確定查詢欄位:員工姓名,工資,職務名稱,職務描述,部門名稱,部門位置,工資等級
SELECT e.`ename`,d.`loc`,s.`grade` FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s ON e.job_id=j.id AND e.dept_id=d.id AND e.salary BETWEEN s.losalary AND hisalary;
2.2.3.1 多表查詢規律總結
- 不管我們查詢幾張表,表連線查詢會產出笛卡爾積,我們需要消除笛卡爾積,拿到正確的資料。我們需要找到表與表之間通過哪個欄位關聯起來的(通常是
外來鍵=主鍵
) - 消除笛卡爾積規律:2張表需要1個條件,3張表需要2個條件,4張表需要3個條件。(條件數量=表的數量-1),每張表都要參與進來
- 多表連線查詢步驟:
3.1. 確定要查詢哪些表
3.2. 確定表連線條件
3.3. 確定查詢欄位
2.2.4 練習4
查詢經理的資訊。顯示員工姓名,工資,職務名稱,職務描述,部門名稱,部門位置,工資等級
具體操作:
1. 確定要查詢哪些表,emp e,salarygrade s
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s;
2. 確定表連線條件 e.job_id=j.id and e.dept_id=d.id and e.salary between s.losalary and hisalary
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s ON e.job_id=j.id AND e.dept_id=d.id AND e.salary BETWEEN s.losalary AND hisalary;
額外條件:只需要查詢經理的資訊(j.jname=‘經理’)
3. 確定查詢欄位:員工姓名,工資,職務名稱,職務描述,部門名稱,部門位置,工資等級
SELECT e.`ename`,s.`grade` FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s ON e.job_id=j.id AND e.dept_id=d.id AND e.salary BETWEEN s.losalary AND hisalary AND j.jname='經理';
2.2.5 練習5
查詢出部門編號、部門名稱、部門位置、部門人數
具體操作:
1. 去員工表中找到每個部門的人數和部門id
SELECT dept_id,COUNT(*) FROM emp GROUP BY dept_id;
2. 再和部門表連線查詢
SELECT * FROM dept d INNER JOIN (SELECT dept_id,COUNT(*) FROM emp GROUP BY dept_id) e ON e.dept_id=d.`id`;
3. 顯示對應的欄位
SELECT d.`id`,d.dname,e.total 部門人數 FROM dept d INNER JOIN (SELECT dept_id,COUNT(*) total FROM emp GROUP BY dept_id) e ON e.dept_id=d.`id`;
最終效果:
第3章 事務安全
3.1 事務的應用場景說明
在實際的業務開發中,有些業務操作要多次訪問資料庫。一個業務要傳送多條SQL語句給資料庫執行。需要將多次訪問資料庫的操作視為一個整體來執行,要麼所有的SQL語句全部執行成功。如果其中有一條SQL語句失敗,就進行事務的回滾,所有的SQL語句全部執行失敗。
例如: 張三給李四轉賬,張三賬號減錢,李四賬號加錢
-- 建立資料表
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,balance DOUBLE
);
-- 新增資料
INSERT INTO account (NAME,balance) VALUES ('張三',1000),('李四',1000);
模擬張三給李四轉500元錢,一個轉賬的業務操作最少要執行下面的2條語句:
- 張三賬號-500
- 李四賬號+500
-- 1. 張三賬號-500
UPDATE account SET balance = balance - 500 WHERE id=1;
-- 2. 李四賬號+500
UPDATE account SET balance = balance + 500 WHERE id=2;
假設當張三賬號上-500元,伺服器崩潰了。李四的賬號並沒有+500元,資料就出現問題了。我們需要保證其中一條SQL語句出現問題,整個轉賬就算失敗。只有兩條SQL都成功了轉賬才算成功。這個時候就需要用到事務
3.2 操作事務
MYSQL中可以有兩種方式進行事務的操作:1.手動提交事務
,2.自動提交事務
3.2.1 手動提交事務
事務有關的SQL語句:
SQL語句 | 描述 |
---|---|
start transaction; | 開啟事務 |
commit; | 提交事務 |
rollback; | 回滾事務 |
手動提交事務使用步驟:
第1種情況:開啟事務 -> 執行SQL語句 -> 成功 -> 提交事務
第2種情況:開啟事務 -> 執行SQL語句 -> 失敗 -> 回滾事務
案例演示1:模擬張三給李四轉500元錢(成功)
目前資料庫資料如下:
-
使用DOS控制檯進入MySQL
-
執行以下SQL語句:
1.開啟事務
,2.張三賬號-500
,3.李四賬號+500
START TRANSACTION; UPDATE account SET balance = balance - 500 WHERE id=1; UPDATE account SET balance = balance + 500 WHERE id=2;
-
使用SQLYog檢視資料庫:發現數據並沒有改變
-
在控制檯執行
commit
提交任務:
-
使用SQLYog檢視資料庫:發現數據改變
案例演示2:模擬張三給李四轉500元錢(失敗)
目前資料庫資料如下:
[外鏈圖片轉存失敗,建議將圖片儲存下來直接上傳(img-gOFG6SlI-1582193672245)(imgs\事務05.png)]
-
在控制檯執行以下SQL語句:
1.開啟事務
,2.張三賬號-500
START TRANSACTION; UPDATE account SET balance = balance - 500 WHERE id=1;
-
使用SQLYog檢視資料庫:發現數據並沒有改變
-
在控制檯執行
rollback
回滾事務:
-
使用SQLYog檢視資料庫:發現數據沒有改變
總結:
如果事務中SQL語句沒有問題,commit提交事務,會對資料庫資料的資料進行改變。
如果事務中SQL語句有問題,rollback回滾事務,會回退到開啟事務時的狀態。
3.2.2 自動提交事務
MySQL的每一條DML(增刪改)語句都是一個單獨的事務,每條語句都會自動開啟一個事務,執行完畢自動提交事務,MySQL預設開始自動提交事務
-
將金額重置為1000
-
執行以下SQL語句
UPDATE account SET balance = balance - 500 WHERE id=1;
-
使用SQLYog檢視資料庫:發現數據已經改變
通過修改MySQL全域性變數"autocommit",取消自動提交事務
使用SQL語句:show variables like '%commit%';
檢視MySQL是否開啟自動提交事務
0:OFF(關閉自動提交)
1:ON(開啟自動提交) -
取消自動提交事務,設定自動提交的引數為OFF,執行SQL語句:
set autocommit = 0;
-
在控制檯執行以下SQL語句:張三-500
UPDATE account SET balance = balance - 500 WHERE id=1;
-
使用SQLYog檢視資料庫,發現數據並沒有改變
-
在控制檯執行
commit
提交任務
-
使用SQLYog檢視資料庫,發現數據改變
3.3 事務原理
事務開啟之後,所有的操作都會臨時儲存到事務日誌,事務日誌只有在得到`commit`命令才會同步到資料表中,其他任何情況都會清空事務日誌(rollback,斷開連線)
3.4 回滾點
在某些成功的操作完成之後,後續的操作有可能成功有可能失敗,但是不管成功還是失敗,前面操作都已經成功,可以在當前成功的位置設定一個回滾點。可以供後續失敗操作返回到該位置,而不是返回所有操作,這個點稱之為回滾點。
設定回滾點語法:savepoint 回滾點名字;
回到回滾點語法: rollback to 回滾點名字;
具體操作:
-
將資料還原到1000
-
開啟事務
-
讓張三賬號減3次錢
UPDATE account SET balance = balance - 10 WHERE id=1; UPDATE account SET balance = balance - 10 WHERE id=1; UPDATE account SET balance = balance - 10 WHERE id=1;
-
設定回滾點:
savepoint abc;
-
讓張三賬號減4次錢
UPDATE account SET balance = balance - 10 WHERE id=1; UPDATE account SET balance = balance - 10 WHERE id=1; UPDATE account SET balance = balance - 10 WHERE id=1; UPDATE account SET balance = balance - 10 WHERE id=1;
-
回到回滾點:
rollback to abc;
-
分析過程
-
如果是事務回滾到指定的位置上,如果需要生效,那麼必須要commit提交。
總結:設定回滾點可以讓我們在失敗的時候回到回滾點,而不是回到事務開啟的時候。
3.5 事務的四大特性
3.5.1 事務的四大特性
事務特性 | 含義 |
---|---|
原子性(Atomicity) | 事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。 |
一致性(Consistency) | 事務前後資料的完整性必須保持一致 |
隔離性(Isolation) | 是指多個使用者併發訪問資料庫時,一個使用者的事務不能被其它使用者的事務所幹擾,多個併發事務之間資料要相互隔離,不能相互影響。 |
永續性(Durability) | 指一個事務一旦被提交,它對資料庫中資料的改變就是永久性的,接下來即使資料庫發生故障也不應該對其有任何影響 |
3.5.2 事務的隔離級別
事務在操作時的理想狀態:多個事務之間互不影響,如果隔離級別設定不當就可能引發併發訪問問題。
併發訪問的問題 | 含義 |
---|---|
髒讀 | 一個事務讀取到了另一個事務中尚未提交的資料 |
不可重複讀 | 一個事務中兩次讀取的資料內容不一致,要求的是一個事務中多次讀取時資料是一致的,這是事務update時引發的問題 |
幻讀 | 一個事務中兩次讀取的資料的數量不一致,要求在一個事務多次讀取的資料的數量是一致的,這是insert或delete時引發的問題 |
MySQL資料庫有四種隔離級別:上面的級別最低,下面的級別最高。“是”表示會出現這種問題,“否”表示不會出現這種問題。
級別 | 名字 | 隔離級別 | 髒讀 | 不可重複讀 | 幻讀 | 資料庫預設隔離級別 |
---|---|---|---|---|---|---|
1 | 讀未提交 | read uncommitted | 是 | 是 | 是 | |
2 | 讀已提交 | read committed | 否 | 是 | 是 | Oracle和SQL Server |
3 | 可重複讀 | repeatable read | 否 | 否 | 是 | MySQL |
4 | 序列化 | serializable | 否 | 否 | 否 |
MySQL事務隔離級別相關的命令
-
查詢全域性事務隔離級別
show variables like '%isolation%'; -- 或 select @@tx_isolation;
-
設定事務隔離級別,需要退出MSQL再進入MYSQL才能看到隔離級別的變化
set global transaction isolation level 級別字串; -- 如: set global transaction isolation level read uncommitted;
3.5.2.1 髒讀的演示
將資料進行恢復:UPDATE account SET balance = 1000;
-
開啟A視窗登入MySQL,設定全域性的隔離級別為最低
mysql -uroot -proot set global transaction isolation level read uncommitted;
-
開啟B視窗,AB視窗都開啟事務
use day23; start transaction;
-
A視窗更新2個人的賬戶資料,未提交
update account set balance=balance-500 where id=1; update account set balance=balance+500 where id=2;
-
B視窗查詢賬戶
select * from account;
-
A視窗回滾
rollback;
-
B視窗查詢賬戶,錢沒了
髒讀非常危險的,比如張三向李四購買商品,張三開啟事務,向李四賬號轉入500塊,然後打電話給李四說錢已經轉了。李四一查詢錢到賬了,發貨給張三。張三收到貨後回滾事務,李四的再檢視錢沒了。
解決髒讀的問題:將全域性的隔離級別進行提升
將資料進行恢復:UPDATE account SET balance = 1000;
-
在A視窗設定全域性的隔離級別為
read committed
set global transaction isolation level read committed;
-
B視窗退出MySQL,B視窗再進入MySQL
-
AB視窗同時開啟事務
-
A更新2個人的賬戶,未提交
update account set balance=balance-500 where id=1; update account set balance=balance+500 where id=2;
-
B視窗查詢賬戶
-
A視窗commit提交事務
-
B視窗檢視賬戶
結論:read committed的方式可以避免髒讀的發生
3.5.2.2 不可重複讀的演示
將資料進行恢復:UPDATE account SET balance = 1000;
-
開啟A視窗
set global transaction isolation level read committed;
-
開啟B視窗,在B視窗開啟事務
start transaction; select * from account;
-
在A視窗開啟事務,並更新資料
start transaction; update account set balance=balance+500 where id=1; commit;
-
B視窗查詢
select * from account;
兩次查詢輸出的結果不同,到底哪次是對的?不知道以哪次為準。
很多人認為這種情況就對了,無須困惑,當然是後面的為準。我們可以考慮這樣一種情況,比如銀行程式需要將查詢結果分別輸出到電腦螢幕和發簡訊給客戶,結果在一個事務中針對不同的輸出目的地進行的兩次查詢不一致,導致檔案和螢幕中的結果不一致,銀行工作人員就不知道以哪個為準了。
解決不可重複讀的問題:將全域性的隔離級別進行提升為:repeatable read
將資料進行恢復:UPDATE account SET balance = 1000;
-
A視窗設定隔離級別為:
repeatable read
set global transaction isolation level repeatable read;
-
B視窗退出MySQL,B視窗再進入MySQL
start transaction; select * from account;
-
A視窗更新資料
start transaction; update account set balance=balance+500 where id=1; commit;
結論:同一個事務中為了保證多次查詢資料一致,必須使用
repeatable read
隔離級別
3.5.2.3 幻讀的演示
在MySQL中無法看到幻讀的效果。但我們可以將事務隔離級別設定到最高,以擋住幻讀的發生
將資料進行恢復:UPDATE account SET balance = 1000;
-
開啟A視窗
set global transaction isolation level serializable; -- 設定隔離級別為最高
-
A視窗退出MySQL,A視窗重新登入MySQL
start transaction; select count(*) from account;
-
再開啟B視窗,登入MySQL
-
在B視窗中開啟事務,新增一條記錄
start transaction; -- 開啟事務 insert into account (name,balance) values ('LaoWang',500);
-
在A視窗中commit提交事務,B視窗中insert語句會在A視窗事務提交後立馬執行
-
在A視窗中接著查詢,發現數據不變
select count(*) from account;
-
B視窗中commit提交當前事務
-
A視窗就能看到最新的資料
結論:使用serializable隔離級別,一個事務沒有執行完,其他事務的SQL執行不了,可以擋住幻讀