1. 程式人生 > 其它 >MySQL03:事務和索引

MySQL03:事務和索引

事務

事務執行基本要素(ACID)

原子性(Atomicity):事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生

一致性(Consistency):事務前後資料的完整性必須保持一致

隔離性(Isolation):資料庫為每一個使用者開啟的事務,不能被其他事務的操作資料所幹擾,多個併發事務之間要相互隔離

永續性(Durability):事務一旦被提交,它對資料庫中資料的改變就是永久性的,即使資料庫發生故障也不應該對其有任何影響

事務隔離

不隔離導致的問題

髒讀:一個事務讀取了另外一個事務未提交的資料

不可重複讀:在一個事務內讀取表中的某一行資料,多次讀取結果不同(這個不一定是錯誤,只是某些場合不對)

虛讀(幻讀):在一個事務內讀取到了別的事務插入的資料,導致前後讀取數量總量不一致

手動提交事務

MySQL預設開啟了事務自動提交

CREATE DATABASE `shop`;

USE `shop`;

CREATE TABLE `account`(
    `id` INT(3) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(20) NOT NULL,
    `money` DECIMAL(9, 2) NOT NULL,
    PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `account`(`name`, `money`) VALUES ('A', 2000), ('B', 1000);

SET autocommit = 0; -- 關閉事務自動提交

START TRANSACTION; -- 手動開啟事務

UPDATE `account` SET `money` = `money` - 500 WHERE `name` = 'A';
UPDATE `account` SET `money` = `money` + 500 WHERE `name` = 'B';

COMMIT; -- 提交

ROLLBACK; -- 回滾

SET autocommit = 1; -- 開啟事務自動提交

SAVEPOINT 儲存點名; -- 設定事務還原點
ROLLBACK TO SAVEPOINT 儲存點名; -- 回滾還原點
RELEASE SAVEPOINT 儲存點名; -- 刪除還原點

事務隔離級別

SELECT @@tx_isolation; -- 查詢當前事務隔離級別
SET TRANSACTION ISOLATION LEVEL 級別; -- 設定事務隔離級別
隔離級別 描述
Serializable 可避免髒讀、不可重複讀、虛讀情況的發生(序列化)
Repeatable read 可避免髒讀、不可重複讀情況的發生(可重複讀)
Read committed 可避免髒讀情況發生(讀已提交)
Read uncommitted 最低級別,以上情況均無法保證(讀未提交)

索引

索引是幫助MySQL高效獲取資料的資料結構,可以大大加快資料的查詢速度

,但建立和維護索引組要耗費時間,因此索引不是越多越好

索引原理

在資料庫查詢中,減少磁碟訪問時資料庫的效能優化的主要手段

為什麼索引能提升資料庫查詢效率呢?根本原因就在於索引減少了查詢過程中讀寫磁碟的IO次數

那麼它是如何做到的呢?使用B+樹

B+樹

B樹每個節點中不僅包含資料的key值,還有data值,要儲存同樣多的key,就需要增加樹的高度。樹的高度每增加一層,查詢時的磁碟I/O次數就增加一次,進而影響查詢效率

而在B+Tree中,資料只出現在葉子節點,所有葉子節點增加了一個鏈指標可以相互訪問,大大增加了每個節點儲存的key值數量,降低了樹的高度

聚集索引與非聚集索引

聚集索引:

  • 索引數值和實體地址是保持一致順序的,索引較大的行,其實體地址也比較靠後
  • 與非聚集索引相比,有著更快的檢索速度,但當表發生資料增刪改時,索引樹也要相應修改,導致開銷更大一些
  • 建立條件:
    • 在InnoDB中,聚集索引預設就是主鍵索引
    • 如果表中沒有定義主鍵,那麼該表的第一個唯一非空索引被作為聚集索引
    • 如果沒有主鍵也沒有合適的唯一索引,那麼InnoDB內部會生成一個隱藏的主鍵作為聚集索引

非聚集索引:

  • 非聚集索引葉子節點上儲存的是索引欄位自身值和主鍵索引

  • 回表二次查詢:使用聚集索引查詢可以直接定位到記錄,而非聚集索引需要掃描兩遍索引樹,即先通過非聚集索引定位到主鍵值,再通過聚集索引定

    位到目標值,效能比聚集索引低

聯合索引(多列索引/複合索引/組合索引)

聚集索引會導致回表二次查詢,解決方法是建立聯合索引,該索引指向多個欄位,但是B+樹只能根據一個欄位來構建,因此依據最左邊的欄位來構建B+樹

最左匹配原則:因為聯合索引是依據最左邊的欄位來構建的,因此查詢時必須要先查左邊的欄位,才能查剩下的欄位,左邊欄位稱為右邊欄位的前導列

索引分類

主鍵索引(PRIMARY KEY)

唯一標識、只能存在一個

不允許值重複或者值為空

唯一索引(UNIQUE KEY)

唯一標識、可存在多個不同的唯一索引

建立唯一性索引的目的不是為了提高訪問速度,而是為了避免資料出現重複,但允許有空值

全文索引(FULLTEXT KEY)

用來查詢文字中的關鍵字,只能在 CHAR、VARCHAR 或 TEXT型別的列上建立,允許插入重複值和空值

普通索引(KEY)

沒有任何限制,唯一目的就是加快系統對資料的訪問速度,在定義索引的列中可以插入重複值和空值

索引的建立

SHOW INDEX FROM `account`; -- 顯示所有的索引資訊

CREATE TABLE 表名 (欄位名 欄位型別 [,...], 索引型別(欄位名 [,...])); -- 在建立表的同時建立
CREATE 索引型別 索引名 ON 表名 (欄位名 [,...]) -- 在已有的表上建立索引,但該語句不能建立主鍵
ALTER TABLE 表名 ADD 索引型別 索引名(欄位名 [,...]); -- 在已有的表上建立索引

索引原則

索引不是越多越好

不要對經常變動的的資料加索引

小資料量的表不需要加索引

索引一般加在經常要被查詢的欄位上

索引的失效

1、WHERE條件包含多欄位,但其中有欄位沒有索引時,不會使用索引

2、多列索引中違反最左匹配原則,不會使用索引

3、查詢條件不是等值或範圍查詢時,更傾向於全表掃描而不會使用索引

4、使用OR條件時,除非每個欄位都有索引,否則不會使用索引

5、模糊查詢時,如果萬用字元%在字串最前面,會導致索引失效而進行全表查詢

6、在欄位名上進行計算或者使用函式,會導致索引失效

MySQL優化

EXPLAIN

EXPLAIN命令模擬優化器執行SQL語句,從而知道MySQL是如何處理SQL語句,分析查詢語句和表結構的效能瓶頸

作用:可以看到表的讀取順序、資料讀取操作的操作型別、哪些索引可以使用、哪些索引被實際使用、表之間的引用、每張表有多少行被優化器查詢

EXPLAIN SELECT * FROM 表名; -- 在SQL語句前面加上EXPLAIN即可

常用結果分析

欄位 釋義
id 按SELECT語句出現的順序增長,值越大執行優先順序越高,值相同則從上往下執行,值為 NULL 最後執行
select_type 表示查詢型別,如簡單查詢(SIMPLE)、複雜查詢(PRIMARY)、子查詢(SUBQUERY)等
type 表示關聯型別或訪問型別,如根據主鍵索引(system、const)、唯一或普通索引(ref)、範圍查詢(range)、全表查詢(index)
key 實際使用的索引
rows 大致估算出找到所需的記錄所需要讀取的行數
Extra 額外資訊,如索引覆蓋(Using index)、前導列(Using index condition)、回表(NULL)、WHERE過濾(Using where)等

百萬級資料庫測試

-- 建立表
CREATE TABLE `app_user`(
    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(50) DEFAULT '' COMMENT '使用者暱稱',
    `email` VARCHAR(50) NOT NULL COMMENT '使用者郵箱',
    `phone` VARCHAR(20) DEFAULT '' COMMENT '手機號',
    `gender` TINYINT(4) UNSIGNED DEFAULT '0' COMMENT '性別(0:男;1:女)',
    `password` VARCHAR(100) NOT NULL COMMENT '密碼',
    `age` TINYINT(4) DEFAULT '0' COMMENT '年齡',
    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `update_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='app使用者表';

-- 定義函式,插入100萬條資料
DELIMITER $$

CREATE FUNCTION mock_data()
RETURNS INT

BEGIN
    DECLARE num INT DEFAULT 1000000;
    DECLARE i INT DEFAULT 0;
    
    WHILE i < num DO
    
        INSERT INTO `app_user`(`name`, `email`, `phone`, `gender`, `password`, `age`) 
        VALUES (CONCAT('使用者名稱', i), '[email protected]', CONCAT('18', FLOOR(RAND() * ((999999999-100000000) + 100000000))),
        FLOOR(RAND() * 2), UUID(), FLOOR(RAND() * 2) * 100);
        SET i = i + 1;
        
    END WHILE;
    
    RETURN i;
END;

-- 執行函式
SELECT mock_data();

-- 測試沒有索引時查詢時間
SELECT * FROM `app_user` WHERE `name`='使用者名稱10000'; -- 0.901 sec
EXPLAIN SELECT * FROM `app_user` WHERE `name`='使用者名稱10000'; -- type:ALL,rows:992168

-- 測試有索引時查詢時間
CREATE INDEX `id_app_user_name` ON `app_user`(`name`); -- 建立索引
SELECT * FROM `app_user` WHERE `name`='使用者名稱10000'; -- 0.010 sec
EXPLAIN SELECT * FROM `app_user` WHERE `name`='使用者名稱10000'; -- type:ref,rows:1