「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!」
當咱們對一張表發起查詢的時候,是否是這張表的數據越少,查詢的就越快?
答案是不必定,這和mysql B+數索引結構有必定的關係。前端
從Innodb存儲引擎的邏輯存儲結構來看,全部數據都被邏輯的放在一個表空間(tablespace)中,默認狀況下,全部的數據都放在一個表空間中,固然也能夠設置每張表單獨佔用一個表空間,經過innodb_file_per_table
來開啓。mysql
mysql> show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_file_per_table | ON |
+-----------------------+-------+
1 row in set (0.00 sec)
複製代碼
表空間又是由各個段組成的,常見的有數據段,索引段,回滾段等。由於innodb的索引類型是b+樹,那麼數據段就是葉子結點,索引段爲b+的非葉子結點。sql
段空間又是由區組成的,在任何狀況下,每一個區的大小都爲1M,innodb引擎通常默認頁的大小爲16k,通常一個區中有64個連續的頁(64*16k=1M)。後端
經過段咱們知道,還存在一個最小的存儲單元頁。它是innodb管理的最小的單位,默認是16K,固然也能夠經過innodb_page_size來設置爲4K、8K...,咱們的數據都是存在頁中的markdown
mysql> show variables like 'innodb_page_size';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.00 sec)
複製代碼
因此innodb的數據結構應該大體以下:數據結構
b+樹索引的特色就是數據存在葉子結點上,而且葉子結點之間是經過雙向鏈表方式組織起來的。
假設存在這樣一張表:post
CREATE TABLE `user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL DEFAULT '',
`age` int(10) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
複製代碼
對於主鍵索引id,假設它的b+樹結構可能以下:spa
咱們來看看 select * from user where id=30
是如何定位到的。指針
id=30
,判斷在第一層的25-50
之間總結:能夠發現一共發起兩次io,最後加載到內存檢索的時間忽略不計。總耗時就是兩次io的時間。code
經過表結構咱們知道,除了id,咱們還有name這個非彙集索引。因此對於name索引,它的結構可能以下:
咱們來看看 select * from user where name=jack
是如何定位到的。
name=jack
,判斷在第一層的mary-tom
之間select *
,因此經過id再去主鍵索引查找總結:name查詢兩次io,而後經過id再次回表查詢兩次io,加載到內存的時間忽略不計,總耗時是4次io。
以上面的user表爲例,咱們先看看一行數據大概須要多大的空間:經過show table status like 'user'\G
mysql> show table status like 'user'\G
*************************** 1. row ***************************
Name: user
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 10143
Avg_row_length: 45
Data_length: 458752
Max_data_length: 0
Index_length: 311296
Data_free: 0
Auto_increment: 10005
Create_time: 2021-07-11 17:22:56
Update_time: 2021-07-11 17:31:52
Check_time: NULL
Collation: utf8mb4_general_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)
複製代碼
咱們能夠看到Avg_row_length=45
,那麼一行數據大概佔45字節,由於一頁的大小是16k,那麼一頁能夠存儲的數據是16k/45b = 364行數據,這是葉子結點的單page存儲量。
以主鍵索引id爲例,int佔用4個字節,指針大小在InnoDB中佔6字節,這樣一共10字節,從root結點出來多少個指針,就能夠知道root的下一層有多少個頁。由於root結點只有一頁,因此此時就是16k/10b = 1638個指針。
innodb引擎中,每一個頁都包含一個PAGE_LEVEL的信息,用於表示當前頁所在索引中的高度。默認葉子節點的高度爲0,那麼root頁的PAGE_LEVEL + 1就是這棵索引的高度。
那麼咱們只要找到root頁的PAGE_LEVEL就好了。
經過如下sql能夠定位user表的索引的page_no:
mysql> SELECT b.name, a.name, index_id, type, a.space, a.PAGE_NO FROM information_schema.INNODB_SYS_INDEXES a, information_schema.INNODB_SYS_TABLES b WHERE a.table_id = b.table_id AND a.space <> 0 and b.name='test/user';
+-----------+---------+----------+------+-------+---------+
| name | name | index_id | type | space | PAGE_NO |
+-----------+---------+----------+------+-------+---------+
| test/user | PRIMARY | 105 | 3 | 67 | 3 |
| test/user | name | 106 | 0 | 67 | 4 |
+-----------+---------+----------+------+-------+---------+
2 rows in set (0.00 sec)
複製代碼
能夠看到主鍵索引的page_no=3,由於PAGE_LEVEL在每一個頁的偏移量64位置開始,佔用兩個字節。因此算出它在文件中的偏移量:16384*3 + 64 = 49152 + 64 =49216,再取前兩個字節就是root的PAGE_LEVEL了。
經過如下命令找到ibd文件目錄
show global variables like "%datadir%" ;
+---------------+-----------------------+
| Variable_name | Value |
+---------------+-----------------------+
| datadir | /usr/local/var/mysql/ |
+---------------+-----------------------+
1 row in set (0.01 sec)
複製代碼
user.ibd在 /usr/local/var/mysql/test/
下。
經過hexdump
來分析data文件。
hexdump -s 49216 -n 10 user.ibd
000c040 00 01 00 00 00 00 00 00 00 69
000c04a
複製代碼
000c040 00 01
00 00 00 00 00 00 00 69
00 01就是說明PAGE_LEVEL=1,那麼樹的高度就是1+1=2。
100w的數據表比1000w的數據表查詢更快嗎?經過查詢的過程咱們知道,查詢耗時和樹的高度有很大關係。若是100w的數據若是和1000w的數據的樹的高度是同樣的
,那其實它們的耗時沒什麼區別。