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