學習目標
- 能夠使用內連接進行多表查詢
- 能夠使用左外連接和右外連接進行多表查詢
- 能夠使用子查詢進行多表查詢
- 能夠理解多表查詢的規律
- 能夠理解事務的概念
- 能夠說出事務的原理
- 能夠在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,gender,salary,join_date,dept_id) VALUES('豬八戒','男',3600,'2010-12-02',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('唐僧','男',9000,'2008-08-08',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('白骨精','女',5000,'2015-10-07',3);
INSERT INTO emp(NAME,gender,salary,join_date,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,gender,salary,join_date,dept_id) VALUES('沙僧','男',6666,'2013-02-24',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,2), -- 獎金
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,salary,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,'林沖',3,1006,'2001-02-22','12500.00','5000.00',30),
(1004,'唐僧',2,1009,'2001-04-02','29750.00',NULL,20),
(1005,'李逵',4,1006,'2001-09-28','12500.00','14000.00',30),
(1006,'宋江',2,1009,'2001-05-01','28500.00',NULL,30),
(1007,'劉備',2,1009,'2001-09-01','24500.00',NULL,10),
(1008,'豬八戒',4,1004,'2007-04-19','30000.00',NULL,20),
(1009,'羅貫中',1,NULL,'2001-11-17','50000.00',NULL,10),
(1010,'吳用',3,1006,'2001-09-08','15000.00','0.00',30),
(1011,'沙僧',4,1004,'2007-05-23','11000.00',NULL,20),
(1012,'李逵',4,1006,'2001-12-03','9500.00',NULL,30),
(1013,'小白龍',4,1004,'2001-12-03','30000.00',NULL,20),
(1014,'關羽',4,1007,'2002-01-23','13000.00',NULL,10);
-- 工資等級表
CREATE TABLE salarygrade (
grade INT PRIMARY KEY,
losalary INT,
hisalary INT
);
-- 添加5個工資等級
INSERT INTO salarygrade(grade,losalary,hisalary) VALUES
(1,7000,12000),
(2,12010,14000),
(3,14010,20000),
(4,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`, e.`ename`, e.`salary`, j.`jname`, 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, job j, 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`, e.`salary`, j.`jname`, j.`description`, d.`dname`, 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, job j, 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;
額外條件:只需要查詢經理的信息(j.jname=‘經理’)
3. 確定查詢字段:員工姓名,工資,職務名稱,職務描述,部門名稱,部門位置,工資等級
SELECT e.`ename`, e.`salary`, j.`jname`, j.`description`, d.`dname`, 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 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, d.`loc`, 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,
NAME VARCHAR(10),
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;
-
B窗口查詢
select * from account;
結論:同一個事務中爲了保證多次查詢數據一致,必須使用
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執行不了,可以擋住幻讀