MySQL事務和優化
事務
事務的概念
完成某個獨立業務(功能)的一個或者一組步驟(指令)組成的一個整體,要麼所有步驟全部執行成功,要麼全部失敗。
一旦某個業務(功能)被事務託管(管理),所有步驟執行成功,會提交事務。如果其中一部失敗會回滾事務,回滾到事務開啟之前的狀態。
事務特徵:
原子性:不可再分,轉賬就是一個原子性操作,所有步驟全部成功或者全部失敗
永續性:轉賬成功,雙方的金額會持久化的儲存到儲存裝置中(資料庫)
一致性:轉賬之前和轉賬之後,資料總量不變,例如:張三向李四轉賬 500,轉賬之前(張三1000,李四1000),轉賬之後(張三500,李四1500)
隔離性:事務A和事務B保持隔離
但是多個事務同時操作同一張表,就會發生一系列的問題,需要設定事務隔離級別來解決該問題
例:兩人轉賬
轉賬步驟
場景:張三向李四轉賬500
步驟:判斷張三賬戶餘額有沒有500
開啟事務
張三賬號-500
-- 停電了
李四賬戶+500
當兩個update語句成功執行
提交事務
如果兩個update沒有成功執行,回滾事務(回滾到事務開啟之前的狀態)
1. 開啟事務 2. 執行業務(此時是轉賬) 3. 執行業務全部成功提交事務,否則回滾事務 -- 開啟事務語法: start transaction; -- 提交事務語法: commit; -- 回滾事務語法: rollback; 只有出現異常狀態(catch塊)才會執行回滾的操作
轉賬操作
-
建立一張tb_account表:id、account_name 賬戶名稱、account_balance 餘額
-
插入資料kxh和cl都是1000
create table tb_account( id int(11) auto_increment, account_name varchar(30) not null, account_balance int(11) , primary key (id) )default charset=utf8mb4,engine=INNODB; insert into tb_account(account_name,account_balance)values('zs',1000),('ls',1000);
場景:在不使用事務的情況下轉賬
update tb_account set account_balance = account_balance-500 where id= 1;
停電了
update tb_account set account_balance = account_balance+500 where id= 2;
-- 此時沒有使用事務:部分成功、部分失敗 kxh(500) cl(100)
場景:使用事務來管理轉賬,好處一旦轉賬出現異常操作,立馬回滾
-- 1. 開啟事務
start transaction;
-- 2. 執行轉賬
update tb_account set account_balance = account_balance-500 where id= 1;
停電了
update tb_account set account_balance = account_balance+500 where id= 2;
-- 3. 如果全部成功提交事務,否則回滾事務
rollback;
場景:使用事務來管理轉賬,執行成功提交事務
-- 1. 開啟事務
start transaction;
-- 2. 執行轉賬
update tb_account set account_balance = account_balance-500 where id= 1;
-- 停電了
update tb_account set account_balance = account_balance+500 where id= 2;
-- 3. 如果全部成功提交事務
commit;
事務提交方式
MySQL事務預設是自動提交
沒有設定start transaction 可以不用commit提交,會自動提交
一旦設定了start traansaction 必須手動commit;
Oracle事務預設為手動提交
一旦執行了DML,必須手動commit;
事務四大特徵(ACID)
原子性(Atomicity):所有的資料修改,要麼一起執行,要麼不執行
一致性(Correspondence):所有的資料修改同時得到反應
隔離性(Isolation): 另一個事務需要在此事務結束之後才能執行
永續性(Durability):資料變動是永久的
事務隔離級別
事務隔離級別 | 髒讀(Dirty) | 虛讀/不可重複讀(NonRepeatable Read) | 幻讀(Phantom Read) |
---|---|---|---|
讀未提交(read uncommitted) | √ | √ | √ |
讀已提交(read committed) | × | √ | √ |
可重複讀(repeatable read) | × | × | × |
序列化(serializable) | × | × | × |
serializable:解決所有問題,多個事務同時訪問某一張表,最多一個執行緒進去(其他執行緒訪問表被鎖住了)
讀未提交 (read uncommitted)
最低的一個事務隔離級別,工作中一般不會用
出現的問題:一個事務會讀取到另外一個事務沒有提交的資料(髒讀)
A事務:kxh向cl轉賬(500), A事務開啟了事務,執行了update,但是沒有提交(1000,1000)
B事務:讀取到了A事務沒有提交的資料(500,1000)
演示:髒讀
髒讀步驟:(查詢未提交的資料)
先設定執行 讀未提交(READ UNCOMMITTED)
在啟動事務,更新資料,不提交
再次讀取資料,發現數據已經被修改了,這就是所謂的“髒讀”
提交事務
再次讀取資料
-- 1將事務隔離級別設定為讀未提交
set global transaction isolation level read uncommitted;
-- 重新開啟命令列視窗
-- 2 開啟事務
start transaction;
-- 3 執行轉賬
update tb_account set account_balance = account_balance-500 where id= 1;
-- 停電了
update tb_account set account_balance = account_balance+500 where id= 2;
-- 4 暫時不提交 如果全部成功提交事務
commit;
-- 開啟另外一個命令列視窗
start transaction;
-- 執行select語句
select * from tb_account;
-- 讀取到上一個事務沒有提交的資料(髒讀)
讀已提交 (read committed)
能夠解決髒讀的問題,但是還會引發其他的問題(同一個事務中多次讀取的資料不一致)
演示:解決髒讀
-- 1設定隔離級別讀已提交
set global transaction isolation level read committed;
-- 2 開啟事務
start transaction;
-- 3 執行轉賬
update tb_account set account_balance = account_balance-500 where id= 1;
-- 停電了
update tb_account set account_balance = account_balance+500 where id= 2;
-- 4 如果全部成功提交事務
commit;
-- 開啟另外一個命令列視窗
start transaction;
-- 執行select語句
select * from tb_account;
-- 上一個事務提交
-- 繼續執行select語句
-- 兩次讀取資料不一致(幻讀)
select * from tb_account;
可重複讀 (repeatable read)
例項:解決虛讀(同一事務多次讀取資料不一致)的問題
演示:
-- 1設定隔離級別讀已提交
set global transaction isolation level repeatable read;
-- 2 開啟事務
start transaction;
-- 3 執行轉賬
update tb_account set account_balance = account_balance-500 where id= 1;
-- 停電了
update tb_account set account_balance = account_balance+500 where id= 2;
-- 4 如果全部成功提交事務
commit;
repeatable read解決虛讀的問題,但是會引發幻讀(A事務對所有賬戶添加了100塊錢,B事務新插入了一條資料。A事務無法操作B事務新插入的資料)
幻讀場景:A事務對所有賬戶添加了100塊錢,事務新插入了一條資料(王五). A事務無法操作王五的資料
序列化 (serializable)
序列化:能夠解決所有問題(髒讀、虛讀、幻讀)
-- 1設定隔離級別讀已提交
set global transaction isolation level serializable;
-- 2 開啟事務
start transaction;
-- 3 執行轉賬
update tb_account set account_balance = account_balance-500 where id= 1;
-- 停電了
update tb_account set account_balance = account_balance+500 where id= 2;
-- 4 如果全部成功提交事務
commit;
小結:A事務開啟了,正在操作tb_account表,沒有提交
B事務稍後開啟了,向通過select語句查詢tb_acount表,但是A事務沒有提交,所有B事務查詢不到結果(此時tb_account表被鎖住了)
隔離級別越高,消耗的記憶體資源越多,效能越低
工作中事務隔離級別設定:讀已提交和可重複讀
序列化 serializable類似於Java中的同步synchronize,同一個表(資源)在同一個事務中訪問,其他事務只能在外面等待
查詢事務隔離級別:
-- tx 事務簡稱 isolation 隔離
select @@tx_isolation;
設定事務隔離級別
set global transaction isolation level 隔離級別;
-- 例如:隔離級別設定為可重複讀
-- global 全域性的 level級別
set global transaction isolation level repeatable read;
MySQL優化
儲存引擎
儲存引擎能夠確定你插入的資料在資料檔案中的儲存方式
MySQL有哪些儲存引擎呢?
-- 通過該命令可以查詢儲存引擎
show engines;
-- 使用頻率最高的就是INNODB儲存引擎
-- 特徵:支援事務、支援外來鍵、支援行級鎖(觸發器)
-- INNODB優點:安全、穩定、可以確保資料完整性
-- INNODB缺點:執行DML語句會用到外來鍵,所有會有檢查資料的合法性操作,速度相對其他儲存引擎會慢一點
-- MyISAM:檢索資料最快的儲存引擎,缺點不支援外來鍵,無法確保資料完整性
-- 建立表必須指定儲存引擎和編碼方式
索引
是一種檢索資料的資料結構,建立的索引會儲存到資料庫的資料檔案中
索引分類:
1)B-Tree索引 B表示平衡 支援> >= < <= like 但是不支援in 不支援前後模糊中間精確,也不支援前面模糊後面精確
2)Hash索引類似於HashMap
既然索引類似於書的目錄,那麼是不是所有的列都適合建立索引呢?
不經常檢索的列不適合建立索引(例如:worker表的worker_image)
資料大量重複的列不適合建立索引(例如:worker_sex 除了M就是F)
text和longtext型別的欄位不適合建立索引
那些列適合建立索引?
主鍵列:建立主鍵約束會自動建立一個索引
經常需要where的欄位建立索引
經常group by的列
經常 order by的列建立索引
唯一約束就是一個唯一索引(例如:worker_mobile欄位)
一張表手動建立索引數量不要超過3個,主鍵、唯一除外
因為:建立索引會生成一張目錄,目錄的資料儲存到資料檔案中,如果索引過多,那麼資料檔案會很大,效能反而低下。
所有的分類:
普通索引
-- 為worker表的worker_name 建立普通索引
-- 列名稱表示表的某一列建立索引
-- create index 索引名稱 on 表名稱(列名稱);
create index index_worker_name on worker(worker_name);
show create table worker ;
-- explain 關鍵字執行SQL語句可以檢視SQL語句是否支援索引
explain select * from worker ;
唯一索引
-- 建立唯一索引語法
-- create unique index 索引名稱 on 表名稱(列名稱);
create unique index index_un_dept_loc on dept(loc);
-- 檢視所有是否建立成功語法
-- show create table 表名稱;
show create table dept;
-- 刪除索引語法
-- alter table 表名稱 drop index 索引名稱;
alter table dept drop index index_un_dept_loc ;
聯合索引
特徵:對錶的多個欄位建立索引
多個欄位同時使用者where條件
語法:
-- create index 索引名稱 on 表名稱(欄位1,欄位2);
-- 例子:對emp表的comm和sal列建立聯合索引
create index index_emp_salcomm on emp(sal,comm);
-- 注意:聯合索引的欄位資料型別最好保持一致
-- select ename,job from emp where comm>100 沒有使用索引,因為comm欄位是聯合索引的第二個欄位
-- select ename,job from emp where sal>100 會使用索引
-- select ename,job from emp where sal>100 or comm=100 會使用索引
-- select ename,job from emp where sal>100 and comm=100 會使用索引
全文字索引(5.7版本新增的)
為大資料文字建立索引 ,關鍵字fulltext
-- 語法
-- create fulltext 索引名稱 on 表名稱(列名稱);
-- 示例
create fulltext index fullindex_emp_ename on emp(ename);
-- 注意:全文索引最好在text或者longtext欄位上建立
大資料量的測試
場景1:根據id查詢customer_bak3表的一行記錄,300萬條資料耗時8毫秒,因為建立主鍵就會預設建立一個索引,所以效率高。工作中可以頻繁使用根據主鍵查詢該行的資料
-- 使用到索引,效率高8毫秒
EXPLAIN
select id,cust_name,cust_address,cust_mobile, cust_ticket,cust_desc
from customer_bak3 where id=345678
-- 沒有使用索引耗時1.3秒
EXPLAIN
select id,cust_name,cust_address,cust_mobile, cust_ticket,cust_desc
from customer_bak3 where cust_name='Ronald Bradford663022';
-- 上面例子:根據名稱查詢對應的實體耗時1.3秒,效率相對低下
-- 為customer_bak3表的cust_name欄位建立索引
-- 300W建立索引耗時20.8秒
create index index_customer_name on customer_bak3 (cust_name);
-- 建立索引之後查詢耗時8毫秒
select id,cust_name,cust_address,cust_mobile, cust_ticket,cust_desc
from customer_bak3 where cust_name='Ronald Bradford663022';
-- 使用like查詢
EXPLAIN
-- 前面模糊後面精確 沒有使用索引 耗時1.5秒
-- 前面精確後面模糊 使用了索引 耗時0.002秒
-- 前後模糊中間精確 沒有使用索引 耗時1.6秒
-- in關鍵字使用了索引
select id,cust_name,cust_address,cust_mobile, cust_ticket,cust_desc
from customer_bak3 where cust_name not in ('Ronald Bradford663022');
場景2:為customer_bak3建立唯一索引
-- 為customer_bak3 的cust_mobile建立唯一索引 15秒
create unique index un_customer_mobile on customer_bak3(cust_mobile);
-- 使用索引耗時0.003秒
-- 不使用索引1.5秒
explain
select id,cust_name,cust_address,cust_mobile, cust_ticket,cust_desc
from customer_bak3
where cust_mobile = '13001408124'
-- 刪除索引
alter table customer_bak3 drop index un_customer_mobile;
場景:為customer_bak3的cust_address和cust_desc建立聯合索引
-- 建立聯合索引14.2秒
create index customer_mobile_address on customer_bak3(cust_desc,cust_address);
explain
-- 使用索引2.2秒
-- 不使用索引1.3秒
-- 使用索引效率反而低了
select * from customer_bak3 where cust_desc='CVBCDE' and cust_address='Shenyang';
-- 刪除索引
alter table customer_bak3 drop index customer_mobile_address
SQL語句的優化
1.不要在select語句中有*,需要幾列就寫幾列
2.工作中SQL語句全部使用大寫,即使你使用了小寫,程式執行時也會將其轉換為大寫
3.在資料量大的情況下慎用like (前後模糊中間精確,前面模糊後面精確)
4.少用in和not in 雖然支援索引但是工作中效率不高
-- 查詢沒有為客戶送水的送水工資訊
select id,worker_name,worker_sal,worker_ticheng
from worker
where id not in(
select distinct worker_id from history
);
-- 使用多表聯合查詢代替in
select w.id,w.worker_name,w.worker_sal,w.worker_ticheng
from worker w left join history h on w.id=h.worker_id
where h.worker_id is null
5.工作中鼓勵使用all和any
6.工作中刪除表資料使用truncate關鍵字代替delete
7.儘量少用having,鼓勵使用where
8.order by排序的欄位一定要建立索引
9.group by後面分組的欄位足夠的少
例如:計算工資 group by 送水工,底薪,提成 為三個欄位進行分組,可以做適當的優化,group by w.id ,因為w.id是工人表的唯一標識
10.適當的建立索引:經常where、group by、order by
11.如果where後面有多個欄位,過濾資料多的放在前面
-- 如果cust_address='Shenyang'過濾的資料多將其放在前面,
select * from customer_bak3 where cust_address='Shenyang' and cust_desc='JUIJK';
12.一張表不要超過15個欄位,必須為表建立主鍵,主鍵一定要使用整數型別
- 如果表中的某個欄位(手機號碼),都是唯一的,一定要為其建立唯一索引
- 聯合索引列資料型別最好一致
- 多表聯合查詢,查詢的次數儘量少,工作中可以將多表聯合查詢的資料放入程式的快取中,後面從快取中獲取
- 資料量大的情況下,使用多次提交代替一次提交
JDBC
JDBC是JavaEE的一個技術,負責將資料庫的資料和程式進行互動
使用JDBC編寫一個程式,在Java程式中編寫SQL語句(update), 通過JDBC執行,最終將執行的結果更新到資料庫中
JDBC全稱:Java DataBase Connectivity
使用JDBC連線資料庫:
步驟:
1. 建立在專案下建立lib目錄
2. 把jar包(mysql-connector-java-5.1.40.jar)匯入lib目錄下
3. 載入MySQL驅動包的驅動,將MYSQL驅動包註冊到Java程式中
4. 定義類JDBC_mysql.java
5. 定義Java連線資料庫的URL、使用者名稱、密碼
因為Java和MySQL資料庫不是同一個廠商,也不在同一個程序下,所以Java程式向MySQL資料庫發起連線必須使用URL
URL格式:
主協議:子協議://主機地址:埠/資料庫名稱?連線引數
jdbc:mysql://localhost:3306/j0812?useUnicode=true&characterEncoding=utf8&useSSL=false
主協議:jdbc 子協議:mysql
mysql主機名稱:localhost
mysql埠號:3306 (mysql資料庫安裝的時候預設埠號)
連線引數:useUnicode=true 使用useUnicode編碼
charsetEncoding=utf8 字符集編碼utf8格式
useSSL=false 不使用Socket套接字安全協議
使用者名稱:就是你登入MySQL的使用者名稱 預設 root
密碼:就是你登入MySQL的密碼 預設 root
6. 建立資料庫連線物件Connection,將url、使用者名稱、密碼作為引數來建立連線
7. 定義SQL語句,該語句就是向資料庫傳送的命令
8. 建立執行SQL語句的物件Statement,該物件用來執行SQL語句
9. 執行SQL語句
10. 釋放連線物件的資源
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBC_mysql {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
try {
// 程式執行時告訴JVM我使用的資料庫驅動為MySQL驅動
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/j0812?useUnicode=true&characterEncoding=utf8&useSSL=false";
String user = "root";
String password = "root";
// Java先MySQL發起連線請求
conn = DriverManager.getConnection(url, user, password);
String sql = "update tb_account set account_balance = 5000 where id=1;";
st = conn.createStatement();
// rows 表示受影響行數 如果小於等於0,沒有成功執行update
int rows = st.executeUpdate(sql);
System.out.println(rows);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放連線物件的資源
// 先開啟的後關閉,後開啟的先關閉
try {
st.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}