日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

mysql技术innodb存储引擎读后感_《Mysql技术内幕-InnoDB存储引擎》读书笔记 (一)...

發布時間:2023/12/15 数据库 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mysql技术innodb存储引擎读后感_《Mysql技术内幕-InnoDB存储引擎》读书笔记 (一)... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

@(Mysql)

官方數據庫

下載

導入/data/mysql57/bin/mysql --socket /data/mysql3306/mysql.socket -uroot -ppassword1 < empoo.sql

虛擬機啟動

關閉iptables

開啟sshd

修改root密碼

mkdir /data

mount /dev/sda1 /data

一、Mysql存儲引擎

Mysql概念

數據庫。是文件的集合。這些文件保存數據庫的數據。

實例。是一個進程,用于管理數據庫。

Mysql組成:

連接池組件

管理服務和工具組件

SQL接口組件

查詢分析器組件

優化器組件

緩存組件

插件式存儲引擎

物理文件

存儲引擎

InnoDB

支持事務

行鎖設計

支持外鍵

支持MCVV,實現4種隔離級別

表的行按照主鍵順序存放

最常用的數據庫引擎。5.5后的默認引擎

MyISAM

5.5前的默認

不支持事務

表鎖設計

只緩存索引文件,不緩存數據文件

可以使用myisampack工具進一步壓縮。但是壓縮后只讀。

NDB

集群數據庫

數據放在內存

高可用和并發

Memory

數據放在內存

Mysql臨時表會用這個引擎

不支持TEXT和BLOB字段。所以如果臨時表要用到這兩個字段,會使用MyISAM引擎,而該引擎不緩存數據在內存,所以性能會有影響。

Archive

只支持INSERT SELECT操作

壓縮后存放

適用于高速插入和壓縮功能。例如日志的存儲

Federated

不存放數據,指向遠程Mysql的表

Maria

用于替換MyISAM,

支持緩存和索引

行鎖設計

MVCC功能

連接數據庫的方式

TCP/IP

命令管道或共享內存

UNIX 域套接字,就是--socket的參數。

二、InnoDB體系結構

體系結構由:

后臺線程

存儲引擎內存池

文件

組成

后臺線程

Master Thread 。負責將緩沖池的數據異步刷新到磁盤,包括

臟頁的刷新

合并插入緩存(INSERT BUGGER)

UNDO頁的回收

IO Thread InnoDB大量使用異步IO(AIO)來處理請求,來提高數據庫性能。

有4類IO線程。

write

read

insert buffer

log

除了write和read,其他線程都是只有一個,通過show variables like 'innodb_%io_threads'來查看write和read的線程數

通過show engine innodb status來查看IO線程的情況

Purge Thread。事務被提交后,undolog就不需要了,PurgeThread用來回收已經分配的undo頁

Page Cleaner Thread 1.2以后引入,用于回收臟頁。

內存

緩沖池

Mysql會把數據存儲在硬盤,為了性能,引入了緩沖池,也就是一塊內存區域。

Mysql的數據都是按頁來存儲的。

從硬盤讀取頁后,首先放到緩沖池。

修改數據后,會修改緩沖池的數據,然后定期同步到硬盤

通過show variables like 'innodb_buffer_pool_size'來查看緩沖池的大小,單位是字節。

緩沖池的數據類型有:

索引頁

數據頁

undo頁

插入緩沖頁,insert buffer

自適應哈希索引 adaptive hash index

鎖信息(lock info)

數據字典信息(data dictionary)

Mysql支持多個緩沖池,通過show variables like 'innodb_buffer_pool_instances'來查看

LRU List 、Free List、FlushList

緩沖池是一個很大的內存區域,存儲各種各樣的頁,頁的默認大小是16KB。

緩沖池的數據頁由下面3個列表組成,列表的item都是頁。

LRU List 主要存儲數據頁

Free List 存儲空閑的頁

Flush List 存儲臟頁

LRU List

LRU List使用LRU(Lastest Recent Used)算法:

使用最頻繁的放在列表前端

使用最少的放在末端

當緩沖池不夠的時候,優先釋放末端的頁

新頁進入緩沖池后,放在末端開始37%的位置,這個位置稱為midpoint。通過show variables like 'innodb_old_blocks_pct'來查看。

midpoint前的稱為new,是最活躍的數據

后的稱為old,是最不活躍的數據

如果緩沖池已滿,刪除列表末端的頁

頁從old升級為new,稱為page made yound

頁沒有從old升級為new(應該指一直在old中,直至被刪除),稱為page not made yound

Free List

數據庫啟動時,由于緩沖池是空的,這時頁都存儲在Free列表中(注意Free列表中的頁都是沒有數據的,或者數據已沒有用)

當需要放新的一頁到緩沖池:

* 首先查看Free列表是否有空閑的頁

* 如果有,使用

* 如果沒有。從LRU列表中刪除末端頁。

Flush List

當數據被修改后,會直接寫重做日志和修改緩沖池數據,然后直接返回事務執行成功,這時候數據還沒有落到硬盤的。(如果這時候數據庫宕機,可以通過重做日志來恢復數據)。

所以就會存在一種狀態,就是緩沖池的數據和硬盤的數據是不一致的。這時候緩沖池的這一頁稱為臟頁(注意是緩沖池的數據比硬盤新)。

臟頁就存放在Flush List中,臟頁也會存儲在LRU List中。

Mysql會定期把Flush List的臟頁同步到硬盤,這個操作叫Checkpoint。

----------------------

BUFFER POOL AND MEMORY

----------------------

Total large memory allocated 137428992

Dictionary memory allocated 102398

Buffer pool size 8191

Free buffers 7891

Database pages 300

Old database pages 0

Modified db pages 0

Pending reads 0

Pending writes: LRU 0, flush list 0, single page 0

Pages made young 0, not young 0

0.00 youngs/s, 0.00 non-youngs/s

Pages read 266, created 34, written 36

0.00 reads/s, 0.00 creates/s, 0.00 writes/s

No buffer pool page gets since the last printout

Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s

LRU len: 300, unzip_LRU len: 0

I/O sum[0]:cur[0], unzip sum[0]:cur[0]

通過show engine innodb status命令可以查看InnoDB的當前狀態。

BUFFER POOL AND MEMORY 是緩沖池信息

Buffer pool size 8191 是緩沖池總大小,單位是頁數量

Free buffers 7891 Free List的大小

Database pages LRU List的大小

Old database pages LRU List中old部分的大小

Modified db pages Flush List的大小

LRU len: 300, unzip_LRU len: 0 LRU List的大小和壓縮數據列表的大小

Checkpoint

當事務提交時:

寫重做日志

修改緩沖池的頁數據

定期執行checkpoint,把緩沖池的臟頁刷新到硬盤

執行checkpoint的時機:

Sharp Checkpoint 在數據庫關機的時候執行。

Fuzzy Checkpoint。數據庫在運行時定期執行

Master Thread Checkpoint 由主線程執行,每1秒或者10秒執行一次

FLUSH_LRU_LIST Checkpoint

Async/Sync Flush Checkpoint

Dirty Page too much Checkpoint

Master Thread

Master Thread有多個循環:

主循環

后臺循環

刷新循環

暫停循環

主循環包含兩個循環:1秒和10秒(不一定準確是1秒或者10秒,有可能有延遲)

1秒

日志緩沖刷新到硬盤,即使事務還沒有提交

如果前1秒的系統IO小于5,合并插入緩存(Insert Buffer)

如果緩沖池臟頁比例大于innodb_max_dirty_pages_pct參數,刷線100個臟頁到磁盤

如果當前沒有用戶活動,切換到后臺循環

10秒

如果前10秒的系統IO小于200,刷新100個臟頁到磁盤

合并最多5個插入緩沖

將日志緩沖刷新到磁盤

刪除無用的Undo頁

如果臟頁比例大于70%,刷新100個臟頁到磁盤,否則10個

問題:

插入緩沖是什么來的

為什么還要刷新日志緩沖到磁盤,不是已經到磁盤了嗎?

Undo頁是什么來的

關鍵特性

插入緩沖

兩次寫

自適應哈希索引

異步IO

刷新鄰接頁

插入緩沖

磁盤分為順序IO和隨機IO,例如要插入數據到磁盤的兩個地方A和B

順序IO是指A和B是磁盤中連續的,或者間隔較少的兩個位置。這樣磁盤通過尋址找到A位置后,可以快速地找到B位置。這樣插入AB和個位置的數據就會比較快,因為只需要1次尋址操作。

隨機IO是指A和B兩個位置沒有關系。當磁盤找到A位置后,需要再次通過尋址操作來尋址B。所以就會比較慢,因為需要2次尋址操作。

數據存儲

Mysql的數據存儲是根據主鍵來順序存儲的。所以在插入數據的時候:

如果主鍵是順序的,它們會存儲在順序的磁盤地址,這樣就是順序IO存儲。例如主鍵是1和2

如果主鍵是不存儲的,Mysql需要存儲在不同地方,這樣就是隨機IO。例如主鍵是abc和bcd

索引存儲

Mysql中主鍵本身也是索引,稱為主索引(Primary index),其他索引稱為輔助索引(secondary index)

如果一個表中有輔助索引,在插入數據的時候,除了存儲數據,還需要建立索引。

如果插入兩條數據,主鍵是順序的,但是有一個索引的數據是不順序的,這樣也會產生隨機IO。Mysql的優化方法是加入插入緩沖:也就是先把索引放在緩沖池中,定期刷新到磁盤。這樣可以減少隨機IO的次數。例如插入兩條數據,索引位置是相同的,如果分兩次刷新到磁盤,就需要兩次隨機IO。如果合并為一次,只需要一次隨機IO。

插入緩沖需要條件:

索引是輔助索引

索引不是唯一的。因為如果是唯一的,插入的時候Mysql需要一次隨機IO找到索引位置,看數據有沒有重復。這樣這次隨機IO就肯定需要的,所以使用插入緩沖來優化就沒有意義了。

可以通過engine status命令來查看插入緩沖的狀態

Ibuf: size 1, free list len 0, seg size 2, 0 merges

merged operations:

insert 0, delete mark 0, delete 0

discarded operations:

insert 0, delete mark 0, delete 0

seg size 是插入緩沖的大小

size 是已經合并的頁數

free list 是空閑的頁數

merged operations 被合并的操作

插入緩沖是一棵B+樹。由非葉子節點和葉子節點組成:

非葉子節點

space 存儲spaceid是表的ID

marker 用于兼容舊版本

offset 頁所在的偏移量

葉子節點

sapce

marker

offset

metadata

具體數據

這樣存儲的好處是把同一個表,相鄰的索引(offset)放在一起。當需要刷新緩沖數據到磁盤的時候,可以把一個表的N個索引合并在一起,通過一次隨機IO就能刷新好。提升刷新的效率。

合并插入緩沖(也就把緩沖刷到磁盤)時機:

輔助索引頁被讀取到緩沖池時。當執行select操作,需要用到對應的索引時,Mysql會檢查插入緩沖是否有該表的該索引緩沖,如果有,立刻刷新到磁盤。因為如果不刷,會影響查詢的準確性。

Insert Buffer Bitmap 頁追蹤到索引頁已沒有可用空間時。緩沖沒有空間的時候,立刻刷新。

Master Thread。主循環會定期刷新。

兩次寫

兩次寫特性用于保證數據的可靠性。

當Mysql刷新緩沖池的一個頁,16KB到磁盤,如果寫到4K的時候,宕機了,怎么辦?

可以通過重做日志來恢復。但是重做日志只會記錄在這一頁的哪個位置寫入內容,例如在偏移量800寫入"aaaa"。如果這一頁都已經損壞了,那重做日志也恢復不了。

Mysql的解決方法是通過兩次寫來解決

把緩沖池的一頁寫入到磁盤的步驟:

把磁盤的數據例如Page1讀入到內存

把Page1保存到共享表空間

寫入緩沖池的數據到磁盤

其實就是在正式寫入前,先把頁的舊數據備份一次,當寫失敗的時候,恢復的時候,從備份還原舊數據,然后再進行新數據的寫入。

mysql> show global status like 'innodb_dblwr%'

-> ;

+----------------------------+-------+

| Variable_name | Value |

+----------------------------+-------+

| Innodb_dblwr_pages_written | 42 |

| Innodb_dblwr_writes | 6 |

+----------------------------+-------+

Innodb_dblwr_pages_written 是雙寫寫了多少個頁

Innodb_dblwr_writes 是實際寫了多少次

一般這兩個數據是8:1(不知道為什么?是不是因為備份8個頁才會正式寫入一次到磁盤?)

自適應哈希索引

又稱為AHI。

這個是Mysql通過觀察查詢情況,對應熱點數據進行自動建立索引。

建立的是hash索引,存放在緩沖池,復雜度是O(1),所以速度是非常快的。

例如我們查詢select * from table where name='a'

當這個查詢進行了超過100次,Mysql就會建立AHI

通過engine status可以查看當前的哈希索引狀態

0.00 hash searches/s, 0.00 non-hash searches/s

hash searches/s是通過哈希索引查詢數,每秒

non-hash 是沒有通過哈希索引的查詢數,每秒

異步IO(AIO)

異步IO對應的是同步IO。

異步IO的優點是:

減少IO的等待時間,例如3次IO,只需要等1個IO時間

合并IO操作,減少隨機IO

例如我們要查詢3個頁,(space,page_no)分別是(8,6),(8,7),(10,9)

如果使用同步IO,我們需要發送3次IO請求,然后等待3次IO時間,明顯這是比較耗時的。

如果使用異步IO,我們一次性發送3個IO請求,然后等待IO結果。AIO發現前兩個IO是連續的,所以可以合并為從8,7開始取16*2KB數據,把3個IO合并為2個,然后只需要1次IO操作時間。

刷新鄰近頁

在刷新臟頁的時候,把相鄰的臟頁一起刷新。相鄰是指兩個頁在磁盤中屬于同一個區。底層就是減少隨機IO的次數。

啟動關閉和恢復

innodb_fash_shutdown

0 數據庫關閉時只需full purge和merge insert buffser操作

1 不進行上面的操作,但是會刷新緩沖池的臟頁回磁盤

2 不進行上面的操作,下次啟動時只需recovery

innodb_force_recovery

0 只需所有的恢復操作

1-6 進行部分的恢復操作

三、文件

Mysql的文件分為:

參數文件,例如my.cnf

日志文件,包括各種日志,例如:

錯誤日志

二進制日志

慢查詢日志

查詢日志

socket文件,用于通過UNIX域套接字方式連接

pid文件,用于記錄Mysql的進程ID

表結構文件,存儲表的結構

存儲引擎文件,存儲表的。

1. 參數文件

是Mysql啟動時的配置文件,Mysql尋找路徑是:

[root@livedvd ~]# /data/mysql57/bin/mysql --help |grep my.cnf

/etc/my.cnf /etc/mysql/my.cnf /usr/local/mysql/etc/my.cnf ~/.my.cnf

也可以在執行mysqld的時候,加參數--defaults-file=/data/mysql3306/my.cnf來指定這個文件。

配置文件使用鍵值對的形式進行存儲,例如innodb_buffer_pool_size=1G。如果在配置文件找不到對應的配置,Mysql會使用默認的配置。

參數類型

動態參數??梢栽贛ysql運行期間進行修改。

靜態參數。不可以在Mysql運行期間進行修改。

查看參數的方式:

show [global] variables like 'innodb_%'

查找infomation_schema庫下面的GLOBAL_VARIABLES表

修改參數:

SET [global | session] name=value 例如:

SET session read_buffer_size=2000000

SET [@@global. | @@session. | @@] name=value 例如

SET @@session.read_buffer_size=2000000

參數分為全局和當前會話。修改參數后,并不會修改配置文件,所以下次啟動,還是會是舊的參數。

2.日志文件

錯誤日志

記錄Mysql的錯誤信息和警告信息。

通過show variables like 'log_error' 來定位日志文件

慢查詢日志

Mysql會把超過一定閾值的Sql記錄到慢查詢日志。

相關的參數有:

long_query_time 表示執行時間超過多少秒(注意是大于,不是大于等于),就記錄到慢查詢日志。

log_slow_queries 表示是否記錄慢查詢日志。設置為ON,才會記錄慢查詢日志。5.7以后是slow_query_log

log_queries_not_using_indexes 是否記錄沒有使用索引的語句。設置為ON后,沒有使用索引的語句也會被記錄

log_throttle_queries_not_using_indexes 每分鐘記錄沒有使用索引的語句條數。這個參數避免打開log_queries_not_using_indexes 后導致慢查詢日志過多

log_output 日志輸出方式。默認是FILE,可以設置為TABLE,日志會輸出到mysql庫的slow_log表。該表是CSV引擎。

long_query_io 超過多少邏輯IO的語句會記錄

slow_query_type 日志記錄方式

0 不記錄

1根據運行時間記錄

根據邏輯IO次數記錄

根據運行時間和邏輯IO記錄

slow_query_log_file 慢查詢日志文件的位置

查詢分為邏輯IO和物理IO

提取慢查詢日志

[root@livedvd ~]# /data/mysql57/bin/mysqldumpslow /data/mysql3306/data/slow.log -s t -r -n 10

Reading mysql slow query log from /data/mysql3306/data/slow.log

Count: 8 Time=0.07s (0s) Lock=0.00s (0s) Rows=1.0 (8), root[root]@localhost

select count(*) from employees where first_name='S'

[root@livedvd ~]#

mysqldumpslow 通過這個命令

-s表示排序 t表示按query time排序

-r表示倒序

-n 表示返回10條語句

通過--help查看更多用法,

也可以使用pt-query-digest工具

查詢日志

會記錄所有的查詢信息。

不知道怎么打開,具體文件在哪里

二進制日志

也就是binary log 也叫binlog。

記錄所有對數據庫的修改信息,用于:

恢復數據庫狀態到某個時間點(point-in-time),需要配合冷備。

復制。主從復制

審計,查看是否有SQL注入

打開binlog日志

修改配置文件,加入配置

[mysqld]

log-bin=binlog

binlog_format=mixed # 日志格式

server-id=1 #節點的ID,可以設置為主庫是1,從庫是2,不能重復。

重啟mysql,show variables like 'log%';查看到log_bin=ON

表示打開了binlog日志

相關參數

binlog相關的參數有:

max_binlog_size 單個binlog日志的最大字節,大于這個字節就會寫入到新的文件。

binlog_cache_szie binlog緩沖區大小。當事務沒有提交時,Mysql會把修改的內容寫入到緩沖區,等commit的時候,寫入到binlog日志。當緩沖區滿了的話,會寫入到臨時文件

binlog_cache_use binlog緩沖區使用次數

binlog_cache_disk_use 臨時文件使用次數

sync_binlog 。binlog的寫入,也會有緩沖區,如果設置這個參數=1,就不會緩沖。這樣可用性會較高,但是性能會較差

binlog-do-db 表示那些庫寫入到binlog

binlog-ignore-db 表示哪些庫不寫入binlog

log-slave-update 是否將從主復制來的修改,寫入到自己的binlog。默認不寫,如果要配置master-slave-slave 就需要寫

binlog_format binlog的格式,可以選:

statement 記錄執行的sql到binlog。優點是省空間,缺點是對于一些隨機語句,可能會導致主從不一致

row 記錄修改的行到binlog 。缺點是費空間

mixed 上面兩個混合。Mysql智能選擇格式

binlog日志查看

mysql> show master status;

+---------------+----------+--------------+------------------+-------------------+

| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |

+---------------+----------+--------------+------------------+-------------------+

| binlog.000002 | 490 | | | |

+---------------+----------+--------------+------------------+-------------------+

1 row in set (0.00 sec)

通過show master status;查看當前的binlog日志的文件名和大小(Position)等信息

binlog的文件在datadir參數的目錄里面。

mysql> show binlog events in 'binlog.000002';

+---------------+-----+----------------+-----------+-------------+---------------------------------------------------------------------+

| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |

+---------------+-----+----------------+-----------+-------------+---------------------------------------------------------------------+

| binlog.000002 | 490 | Anonymous_Gtid | 1 | 555 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |

| binlog.000002 | 555 | Query | 1 | 634 | BEGIN |

| binlog.000002 | 634 | Query | 1 | 758 | use `employees`; update employees set first_name='kevinlu2' limit 1 |

| binlog.000002 | 758 | Xid | 1 | 789 | COMMIT /* xid=24 */ |

+---------------+-----+----------------+-----------+-------------+---------------------------------------------------------------------+

11 rows in set (0.00 sec)

mysql>

通過命令show binlog events in 'binlog.000002'可以看到binlog的內容,如果是statement格式,可以看到執行的sql語句。

/data/mysql57/bin/mysqlbinlog -vv --start-position=0 binlog.000002

通過mysqlbinlog命令也可以查看binlog的信息。其中

-vv表示轉換row格式。如果不轉換,返回二進制信息,看不懂。

可以看到轉換后,row格式的內容是這樣的:

BINLOG '

2gr1XRMBAAAARAAAAG4BAAAAAHEAAAAAAAMACWVtcGxveWVlcwAJZW1wbG95ZWVzAAYDCg8P/goG

DgAQAPcBADPIzgc=

2gr1XR8BAAAAXQAAAMsBAAAAAHEAAAAAAAEAAgAG///AEScAACJDDwdrZXZpbmx1B0ZhY2VsbG8B

2oQPwBEnAAAiQw8Ia2V2aW5sdTEHRmFjZWxsbwHahA8LS+ze

'/*!*/;

### UPDATE `employees`.`employees`

### WHERE

### @1=10001 /* INT meta=0 nullable=0 is_null=0 */

### @2='1953:09:02' /* DATE meta=0 nullable=0 is_null=0 */

### @3='kevinlu' /* VARSTRING(14) meta=14 nullable=0 is_null=0 */

### @4='Facello' /* VARSTRING(16) meta=16 nullable=0 is_null=0 */

### @5=1 /* ENUM(1 byte) meta=63233 nullable=0 is_null=0 */

### @6='1986:06:26' /* DATE meta=0 nullable=0 is_null=0 */

### SET

### @1=10001 /* INT meta=0 nullable=0 is_null=0 */

### @2='1953:09:02' /* DATE meta=0 nullable=0 is_null=0 */

### @3='kevinlu1' /* VARSTRING(14) meta=14 nullable=0 is_null=0 */

### @4='Facello' /* VARSTRING(16) meta=16 nullable=0 is_null=0 */

### @5=1 /* ENUM(1 byte) meta=63233 nullable=0 is_null=0 */

### @6='1986:06:26' /* DATE meta=0 nullable=0 is_null=0 */

執行的sql是useemployees; update employees set first_name='kevinlu1' limit 1,可以看到

即使只修改一個字段,但是row格式會把整行的所有字段都寫進binglog。

從機復制的時候,應該根據表的主鍵來匹配到對應的行,然后使用binlog的數據覆蓋整行數據

3. 套接字文件

mysql> show variables like 'socket';

+---------------+------------------------------+

| Variable_name | Value |

+---------------+------------------------------+

| socket | /data/mysql3306/mysql.socket |

+---------------+------------------------------+

mysqld啟動會,會生成一個socket文件,mysql連接本機的mysql的話,可以直接指定socket文件,這樣就不用輸入端口和host了。

連接方式:

/data/mysql57/bin/mysql --socket /data/mysql3306/mysql.socket -uroot -ppassword1

4. pid文件

參數名是pid_file

5. 表結構定義文件

由于Mysql每個表都有自己的引擎類型,所以Mysql的存儲是以表為單位的。

每個表都會有一個xx.frm文件來描述表的定義。

frm文件在datadir里面的庫文件夾里面。

書上說是文本文件,但是測試發現5.7的mysql是二進制文件。

6.InnoDB存儲引擎文件

表空間文件

表空間文件用于存儲表的數據,索引,插入緩沖等信息。

例如test庫的table1表的表空間文件就是/data/mysql3306/data/test/table1.ibd

相關參數

innodb_data_file_path 定義表空間文件的路徑,可以指定多個文件,這樣Mysql會分開存儲表的數據,如果路徑位于不同的磁盤,可以提升性能。

innodb_file_per_table 是否分開存儲表空間文件。如果等于ON,每個表一個ibd文件,否則整個庫的數據都存儲在一起,idbdata1.ibd。

重做日志文件

如果每次事務提交,Mysql都修改具體的數據文件,性能會比較差。優化方法是先把修改點記錄到重做日志文件。然后定期把重做日志的內容更新到具體的數據文件。這樣做的另一個好處是,當事務提交后,由于已寫入到重做日志,所以Mysql宕機后的重啟,可以通過更新重做日志的內容來實現事務不會丟失。

重做日志叫redo log file。位于data目錄下面的ib_logfile0和ib_logfile1。這兩個文件會循環寫入,例如先寫0,寫滿后寫1,寫滿后再寫0,不斷循環。

innodb_log_file_size單個日志文件的最大大小

innodb_log_files_in_group 日志文件的數據,默認是2,也就是0和1

innodb_mirrored_log_groups 日志鏡像文件組的數量,默認是1. 為了可用性,可以加多個重做日志的鏡像。

innodb_log_group_home_dir 日志文件的目錄,默認是data目錄

重做日志的寫入也有個緩沖區。緩沖區寫入磁盤是按512字節寫入的,所以一定會成功(為什么?)

所以事務的流程

在事務的執行過程中,不斷有信息寫入到重做日志,

在事務提交時,先寫二進制文件,寫入成功后,返回提交成功

四、表

1. 索引組織表

InnoDB中,數據的存儲是根據主鍵來的,也就是主鍵相鄰的數據存儲在一起。

定義表時,

如果有主鍵

如果沒有主鍵

選擇一個唯一的非空的字段作為索引

如果沒有,自動創建一個6字節的指針

所以在InnoDB看來,每個表都有主鍵。

2.InnoDB邏輯存儲結構

所有的數據都存儲在idb文件中,這個稱為表空間(tablespace)

表空間由段(segment),區(extent),頁(page)組成。

表空間由3個段組成:

葉子節點段

非葉子節點段

回滾段

每個段由多個區組成

每個區由多個頁組成

一個頁存放一個或多個行

頁有時也稱為block

分為葉子段,非葉子段,回滾段

任何情況下,一個區的大小是1M

為了保證數據的連續性,Mysql一次會申請4-5個區的空間。

一般一個頁是16K,也就是一個區由64個頁

創建表示,Mysql為了節省空間,只會申請32個頁的空間,稱為碎片頁(fragment page)。后面才會一次申請64個頁。

[root@livedvd test]# py_innodb_page_info.py -v t1.ibd

page offset 00000000, page type

page offset 00000001, page type

page offset 00000002, page type

page offset 00000003, page type , page level <0000>

page offset 00000000, page type

page offset 00000000, page type

Total number of page: 6:

Freshly Allocated Page: 2

Insert Buffer Bitmap: 1

File Space Header: 1

B-tree Node: 1

File Segment inode: 1

每一個頁由自己的偏移量,也可以稱為頁的ID

每個頁都有類型,

這里一共6個頁,其中沒有使用的2個,已使用的4個,

Btree節點頁1個,level0表示這是葉子節點,如果等于1表示非葉子節點

Freshly Allocated Page表示還沒有使用的頁,

上面的是新建表后,插入2行7000長度varchar的行后的數據,一個字符的長度是1字節,所以7000就是7000字節,兩行就是差不多16k。所以一個頁就能把這2行數據存儲,所以上面只有一個btree的頁

如果再插入一行,就需要2個頁了,因為大于1個節點頁,就需要一個非葉子節點,

page offset 00000003, page type , page level <0001>

page offset 00000004, page type , page level <0000>

page offset 00000005, page type , page level <0000>

可以看到,有一個非葉子節點,2個葉子節點。

mysql> DELIMITER //

mysql> create procedure load_t1(count int unsigned) begin declare s int unsigned default 1;

-> declare c varchar(7000) default repeat('a',7000);

-> while s<= count DO

-> insert into t1 select NULL,c;

-> SET s=s+1

-> ;

-> END while;

-> end;

-> //

DELIMITER //表示修改結束符,從;修改為//

如果繼續插入數據,插入64行后,就需要32個頁,這時碎片頁就用完了,如果繼續插入,Mysql就會一次申請64個頁,也就是一個區,1M。

默認一頁是16K,可以修改innodb_page_size為4K,8K,16K。

頁類型有:

數據頁(B-tree Node)

undo頁(undo Log Page)

系統頁(System Page)

事務數據頁(Transaction system Page)

插入緩沖位圖頁(Insert Buffer BItmap)

插入緩沖空閑列表頁(Insert BUffer Free List)

未壓縮的二進制大對象頁(Uncompresssed BLOB Page)

壓縮的二進制大對象頁(compressed BLOB Page)

InnoDB的存儲是面向行的,也就是以行來組織存儲的

3. InnoDb行記錄格式

有4種格式:

Compact

Redundant主要用于兼容舊版本

Dynamic 和Compact一樣,不同點,書上說使用完全行溢出。(5.7的mysql實驗發現并不是)

Compressed 和Dynamic一樣,不同點是對可變長度的數據進行zlib壓縮,包括BLOG,TEXT,VARCHAR

通過show table status like 'employees'命令可以查看表的Row_format

Compact格式

一行數據的組成

變長字段長度列表。例如有3個可變長度的字段,長度分別為1,2,3,這里會存儲03 02 01(逆序存放,3個字段就存放3字節)

如果列長度小于255,使用1字節表示

如果長度小于65535 ,使用2字節表示。

不允許有長度大于65535 的可變列,因為規定varchar的最大長度是65535

NULL標志位。用二進制表示,1表示第N頁是NULL

記錄頭信息,5字節

1bit 未知

1bit 未知

1bit deleted_flag 該行是否刪除

1bit min_rec_flag

4bit n_owned 該記錄所在的槽擁有的記錄數

13bit heap_no 索引堆中該記錄的排序記錄

3bit record_type 記錄類型 000表示普通 001 表示B+書節點指針,010 鄙視Infimum,011表示Supremum 1xx表示保留

16bit next_record 下一條記錄的相對位置

列數據

有兩個固定列:

事務ID列 6字節

回滾指針列 7字節

如果沒有主鍵ID,會自動加一列rowid 6字節

后面就是用戶自己的數據了

變長字段只會一定長度的數據,超出的部分會存儲到Uncompresssed BLOB Page,并在未超出的數據后記錄超出的部分存儲在哪個頁的哪個偏移量。這個稱為行溢出存儲。

為什么超出的部分需要存儲在其他地方?

因為存儲是B+樹形式的,如果一個頁只有一行數據,那邊B+樹就沒有意義了,就變成鏈表了。所以一頁必須存儲大于1行數據。

varchar類型,varchar(N)的N表示最大字符長度,官方說的varchar最大存儲是65535字節,長度和字節是不完全一樣的。

可以通過命令hexdump -C -v t3.ibd > t3.hex查看具體的ibd文件。

000101a0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaaaaaaaa|

61表示一個字節。一行16字節。

char類型。InnoDB中,char類型存儲的不一定是定長的數據。

如果表的字符集是latin1,char存儲的是定長的數據,如果不夠長度,使用ox20來填充。

如果字符集是其他,例如GBK,UTF8,char相當于varchar

4.InnoDB數據頁結構

頁是Mysql最小的磁盤管理單位。

頁的組成:

File Header 文件頭 38字節

Page Header 頁頭 56字節

Infimun 和Supremum Records

User Records 用戶記錄

Free Space 空閑空間

Page Directory 頁目錄

File Trailer 文件結尾信息 8字節

File Header

FIL_PAGESPACE_OR_CHKSUM checksum值 4字節

FIL_PAGE_OFFSET 4字節頁在表空間中的偏移量,也就是第幾個頁

FIL_PAGE_PREV 前一個數據頁的偏移量

FIL_PAGE_NEXT 下一個頁的偏移量

FIL_PAGE_LSN 8字節 LSN(log sequence number)

FIL_PAGE_TYPE 頁類型 2字節

45BF B+樹葉子節點

FIL_PAGE_FILE_FLUSH_LSN 8字節LSN值

FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4字節表示該頁屬于哪個表空間

Page Header

PAGE_N_DIR_SLOTS 2字節 在Page Directory 頁目錄中的Slot(槽)數

PAGE_HEAP_TOP 2字節 空閑空間堆中的最小位置

PAGE_N_HEAP 2字節 堆中的記錄數,也就是該頁中的記錄數

PAGE_FREE 2字節 可重用空間的首指針

PAGE_GARBAGE 2字節 已刪除的記錄數量

PAGE_LAST_INSERT 2字節 最后插入記錄的位置

PAGE_DIRECTION 2字節最后插入的方向

PAGE_N_DIRECTION 2字節 一個方向連續插入記錄的數量

PAGE_N_RECS 2字節 頁中記錄數量

PAGE_MAX_TRX_ID 8字節 修改當前頁的最大事務ID,

PAGE_LEVEL 2字節 頁在索引樹中的位置,00表示葉節點

PAGE_INDEX_ID 8字節 索引ID

PAGE_BTR_SEG_LEAF 10字節

PAGE_BTR_SEG_TOP 10字節

Infinum和Supremum Record

每一頁中都有2行偽記錄,表示當前頁最小值和最大值

User Record 和 Free Space

真正的數據,存儲方式看上面的行存儲描述。

Page Directory

B+Tree只能定位到具體的頁,定位后Mysql會把整個頁加載到內存。然后通過頁的Page Directory找到具體的行。這個尋找方式的復雜度是二分查找。實現方式是通過Page Directory。由于這個操作是二分查找,而且在內存中,所以速度很快。

一個頁里面會有很多個槽,一個槽有多行記錄,槽中的記錄的n_owned值記錄該槽擁有的記錄數,當插入和刪除操作后,Mysql需要對槽進行分裂或者平衡操作。

偽記錄 Infimum 的 n_owned 的值總是1

Supremum的n_owned的值是[1,8]

用戶記錄的n_owned的值是[4,8]

假如有主鍵為 1,2,3,4,5,6,每個槽有4行記錄。則槽的形式可能是:

1234 存放在一個槽1,槽1指向的記錄是1

56存放在一個槽2,槽2指向的是記錄5

File Trailer

這個用于和File Header的checksum檢查,檢測是否一頁數據完整,(已完整寫入到磁盤)。

8字節,前4字節等于Header的checksum后4字節等于LSN。

測試

創建一個新表,并插入3行數據

mysql> create table t5(id int unsigned not null auto_increment,b char(10),primary key (a) );

mysql> insert into t5 set b='aaaaaaaaaa';

mysql> insert into t5 set b='bbbbbbbbbb';

mysql> insert into t5 set b='cccccccccc';

使用py_innodb_page_info工具,查看到數據頁位于第4個頁,使用hexdump -C -v t5.ibd > t5.hex命令,查看表空間文件。第4個頁位于1610243的位置,16進制是 c000。

內容如下:

0000c000 36 a9 8f ef 00 00 00 03 ff ff ff ff ff ff ff ff |6...............|

0000c010 00 00 00 00 06 cd b7 6a 45 bf 00 00 00 00 00 00 |.......jE.......|

0000c020 00 00 00 00 00 2e 00 02 00 db 80 05 00 00 00 00 |................|

0000c030 00 c0 00 02 00 02 00 03 00 00 00 00 00 00 00 00 |................|

0000c040 00 00 00 00 00 00 00 00 00 42 00 00 00 2e 00 00 |.........B......|

0000c050 00 02 00 f2 00 00 00 2e 00 00 00 02 00 32 01 00 |.............2..|

0000c060 02 00 1b 69 6e 66 69 6d 75 6d 00 04 00 0b 00 00 |...infimum......|

0000c070 73 75 70 72 65 6d 75 6d 00 00 00 10 00 21 00 00 |supremum.....!..|

0000c080 00 01 00 00 00 00 0f 80 f4 00 00 02 03 01 10 61 |...............a|

0000c090 61 61 61 61 61 61 61 61 61 00 00 00 18 00 21 00 |aaaaaaaaa.....!.|

0000c0a0 00 00 02 00 00 00 00 0f 81 f5 00 00 02 04 01 10 |................|

0000c0b0 62 62 62 62 62 62 62 62 62 62 00 00 00 20 ff b0 |bbbbbbbbbb... ..|

0000c0c0 00 00 00 03 00 00 00 00 0f 86 f8 00 00 02 07 01 |................|

0000c0d0 10 63 63 63 63 63 63 63 63 63 63 00 00 00 00 00 |.cccccccccc.....|

0000c0e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|

0000c0f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|

后面的都是00來了,因為空間未被使用,找到頁的末尾,內容如下:

0000fff0 00 00 00 00 00 70 00 63 b7 5c cf 11 06 cd 89 e1 |.....p.c.\......|

數據解析(下面的數字都是十六進制):

FIL_PAGESPACE_OR_CHKSUM = b7 5c cf 11

FIL_PAGE_OFFSET = 00 00 00 03 表示這一頁在表空間的偏移位是3

FIL_PAGE_PREV = ff ff ff ff

FIL_PAGE_NEXT =ff ff ff ff 由于只有一個數據頁,前后頁的值都是空

FIL_PAGE_LSN = 00 00 00 00 06 cd 89 e1

FIL_PAGE_TYPE = 45 bf 表示這是個葉子頁

45BF B+樹葉子節點

FIL_PAGE_FILE_FLUSH_LSN 00 00 00 00 00 00 00 00

FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID = 00 00 00 2d

PAGE_N_DIR_SLOTS = 00 02 2個槽

PAGE_HEAP_TOP = 00 db 空閑空間的位置,這一頁的位置是c000 加上 00db就是 c0db,可以看到這個位置的數據都是0

PAGE_N_HEAP = 80 05 因為是Compact格式,初始值是0x8002,所以 頁中的記錄數=8005-8002=3

PAGE_FREE =00 00 由于沒有刪除記錄,所以是0

PAGE_GARBAGE =00 00 由于沒有刪除記錄,所以是0

PAGE_LAST_INSERT = 00 c0 最后插入記錄的位置為c000+00c0 = c0c0

PAGE_DIRECTION =00 02

PAGE_N_DIRECTION =0 02

PAGE_N_RECS = 00 03 頁中記錄數量為3

PAGE_MAX_TRX_ID = 00 00 00 00 00 00 00 00

PAGE_LEVEL =00 00 葉子節點

PAGE_INDEX_ID = 00 00 00 00 00 00 00 41

上面是File Header 和Page Header

下面是兩個偽記錄

infimum:

01 00 02 00 1b 69 6e 66 69 6d 75 6d 00

supremum

04 00 0b 00 00 73 75 70 72 65 6d 75 6d

簽名是record header 5字節,偽記錄只有一列 char(8)內容是supremum和infimum

從infimum的record的后兩字節00 1b,可以計算第一行的位置為c063+001b=c07e。c063是infimum record header之后第一字節的位置(也就是69的位置)。c07e是第一行的內容開始的位置,而不是可變長度數據的位置

所以第一行的數據為

00 00 00 10 00 21 00 00

00 01 00 00 00 00 0f 80 f4 00 00 02 03 01 10 61

61 61 61 61 61 61 61 61 61

c07e是加粗字節的位置,前面5字節是record header,再前面1字節是NULL,沒有可變長度。

00 00 00 01是rowid,由于有組件所以這里是a字段的值,a字段是int類型,占4字節。

00 00 00 00 0f 80 是事務ID

f4 00 00 02 03 01 10 是回滾點

61 61 61 61 61 61 61 61 61 61是除主鍵外第一列的數據,是b字段,char10,10字節,全都是A,61的acii碼對應就是a

同樣的方法,計算第一行的開始位置,等于c07e+0021=C09F

61 61 61 61 61 61 61 61 61 00 00 00 18 00 21 00

00 00 02 00 00 00 00 0f 81 f5 00 00 02 04 01 10

62 62 62 62 62 62 62 62 62 62

C09F是上面加粗的位置,同理向前6字節是null和record header

所以使用這個方法,就能找到頁中所有行。

通過Page Header的next和pre,找到前后的頁。這樣就能找到表中所有的數據。

Page Directory

槽是逆序存放的,一個槽占2字節。File Trailer往前推就是Page DIrectory。

也就是 00 70 00 63

這里有兩個槽,

第一個槽00 63,對應的是0063+c000的位置,就是指向Infimum行的內容(不是header)

第二個槽00 70,對應的是C070,也就是supermum的行的內容,然后找到supermum的頭04 00 0b 00 00,其中n_owned在第一個字節的后面4bit,轉換04為二進制后是00000100,所以n_owned=0100,十進制就是4。所以n_owned=4。表示這個槽有4行記錄,包括super行和其他3行用戶行。(其實直接取04的4也可以)

5 Named File Formats機制

現在InnoDB有4中文件格式,后面也會有更多

6 約束

數據完整性

InnoDB提供的約束有:

Primary Key 該字段的值需要唯一

Unique Key 該字段的值需要唯一

Foreign Key 該字段的值需要in另一個表的一個字段的所有取值

Default 字段設置默認值

NOT NULL 字段不能為NULL

錯誤值約束

如果輸入的值,和字段的數據類型不符合。例如字段是int類型,輸入了'a',這就是錯誤值。

對于錯誤值,InnoDB有兩種做法:

當sql_mode沒有STRICT_TRANS_TABLES,修改值為0,并報Warning

當sql_mode有STRICT_TRANS_TABLES,報Error,拒絕寫入

通過show variables like 'sql_mode'來查看

觸發器約束

可以為一個表設置6個觸發器,分別為INSERT UPDATE DELETE 的BEFORE 和AFTER。表示插入,更新,刪除的前后。

mysql> create table cash (user_id int NOT NULL,cash INT NOT NULL);

Query OK, 0 rows affected (0.02 sec)

mysql> create table err_log(user_id int ,old_cash int,new_cash int,time datetime);

Query OK, 0 rows affected (0.02 sec)

mysql> delimiter $$

mysql> create trigger trg_usercash_update before update on cash

-> for each row

-> begin

-> if new.cash-old.cash > 0 then

-> insert into error_log

-> select old.user_id,old.cash,new.cash,NOW();

-> set new.cash=old.cash;

-> end if;

-> end;

-> $$

創建一個觸發器,當cash表更新的時候,如果更新后cash字段大于更新前,拒絕更新,并寫一條日志

外鍵約束

在批量修改數據時,外鍵約束會導致大量的額外開銷,因為需要查詢對應的約束是否滿足。可以通過set foreign_key_checks=0來取消檢查

7.視圖

視圖是一個虛表,和持久表不同的時候,視圖中的數據沒有實際的物理存儲。

8.分區

Mysql支持的分區類型

RANGE 分區。例如小于10的放在分區1,小于20的放在分區2

LIST 分區,主鍵在1,2,5,6的放在分區1,主鍵在4,7的放在分區2

HASH 分區,按主鍵,計算hash值(怎么計算,由用戶提供,例如取模),放在指定的分區,這樣可以均勻存放

KEY分區,和HASH分區類型,只是計算hash值使用Mysql指定的還是,和password還是一樣

COLUMNS分區,5.5后支持。上面的分區方法都是為每行計算一個int值,然后決定放在哪個分區。COLUMNS分區支持其他數據類型,例如float,datetime等。

子分區,可以在分區的基礎上再分區,又叫復合分區。

分區和性能

分區后的好處

分區后,不同分區存放在不同的文件。所以如果需要把一張表存放在不同的硬盤,就可以使用分區

對于分區的鍵進行查詢,速度會有一定提升。

例如1kw的數據,需要根據key1來查詢,

如果使用索引,1kw數據可以能需要3層B+tree索引,就需要3次磁盤IO

如果使用key1分區,假如有10個分區,那么可能只需要2層索引,這樣可以減少一次IO

其實對查詢速度提升是有的,但是很有限。(可能使用索引也是2層,那就沒有優化了)

刪除單個分區非常快。因為是按分區存放文件的,所以一個分區,就相當于刪除一個ibd文件,速度非???/p>

分區的壞處

分區后。表中的索引在每個分區單獨管理。

假如表t 按key1分區10個,同時有索引idx_1(key2),如果查詢時使用idx1索引,就需要到每個分區執行索引查詢,這時候磁盤IO可能是2*10次。遠大于不分區時2-3次磁盤IO

分區查詢的性能,可以通過explain命令來查看,有一個partitions列,展示本次查詢檢索了哪幾個分區

總的來說,使用分區要謹慎。

分區更適用于OLAP(在線分析處理),這時候一般需要查詢大量數據

不適用于OLTP(在線事務處理),這時候一般都是使用索引查詢幾條數據。

3

創建表create table t7 (a int ) partition by hash(a) partitions 4;,然后有4個ibd文件

t7.frm

t7#P#p0.ibd

t7#P#p1.ibd

t7#P#p2.ibd

t7#P#p3.ibd

總結

以上是生活随笔為你收集整理的mysql技术innodb存储引擎读后感_《Mysql技术内幕-InnoDB存储引擎》读书笔记 (一)...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。