1. 程式人生 > 其它 >MySQL事務和優化

MySQL事務和優化

事務

事務的概念

  完成某個獨立業務(功能)的一個或者一組步驟(指令)組成的一個整體,要麼所有步驟全部執行成功,要麼全部失敗。

  一旦某個業務(功能)被事務託管(管理),所有步驟執行成功,會提交事務。如果其中一部失敗會回滾事務,回滾到事務開啟之前的狀態。

事務特徵:

  原子性:不可再分,轉賬就是一個原子性操作,所有步驟全部成功或者全部失敗

  永續性:轉賬成功,雙方的金額會持久化的儲存到儲存裝置中(資料庫)

  一致性:轉賬之前和轉賬之後,資料總量不變,例如:張三向李四轉賬 500,轉賬之前(張三1000,李四1000),轉賬之後(張三500,李四1500)

  隔離性:事務A和事務B保持隔離

 但是多個事務同時操作同一張表,就會發生一系列的問題,需要設定事務隔離級別來解決該問題

例:兩人轉賬

轉賬步驟

場景:張三向李四轉賬500

步驟:判斷張三賬戶餘額有沒有500

  開啟事務

  張三賬號-500

  -- 停電了

   李四賬戶+500

  當兩個update語句成功執行

   提交事務

  如果兩個update沒有成功執行,回滾事務(回滾到事務開啟之前的狀態)

1. 開啟事務
2. 執行業務(此時是轉賬)
3. 執行業務全部成功提交事務,否則回滾事務

-- 開啟事務語法:   start transaction;
-- 提交事務語法:  commit;
-- 回滾事務語法:  rollback;  只有出現異常狀態(catch塊)才會執行回滾的操作

轉賬操作

  1. 建立一張tb_account表:id、account_name 賬戶名稱、account_balance 餘額

  2. 插入資料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個欄位,必須為表建立主鍵,主鍵一定要使用整數型別

  1. 如果表中的某個欄位(手機號碼),都是唯一的,一定要為其建立唯一索引
  2. 聯合索引列資料型別最好一致
  3. 多表聯合查詢,查詢的次數儘量少,工作中可以將多表聯合查詢的資料放入程式的快取中,後面從快取中獲取
  4. 資料量大的情況下,使用多次提交代替一次提交

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();
			}
		}
	}
}