MySQL Innodb 儲存結構 & 儲存Null值 解析

weixin_34402090發表於2012-10-17

背景:
表空間:INNODB 所有資料都存在表空間當中(共享表空間),要是開啟innodb_file_per_table,則每張表的資料會存到單獨的一個表空間內(獨享表空間)。
獨享表空間包括:資料,索引,插入快取,資料字典。共享表空間包括:Undo資訊(不會回收<物理空間上>),雙寫快取資訊,事務資訊等。
段(segment):組成表空間,有區組成。
區(extent):有64個連續的頁組成。每個頁16K,總共1M。對於大的資料段,每次最後可申請4個區。
頁(page):是INNODB 磁碟管理的單位,有行組成。
行(row):包括事務ID,回滾指標,列資訊等。

目的1:
瞭解表空間各個頁的資訊和溢位行資料儲存的資訊。通過該書作者蔣承堯編寫的工具:http://code.google.com/p/david-mysql-tools/source/browse/trunk/py_innodb_page_type/
3個指令碼:
py_innodb_page_info.py

View Code
#! /usr/bin/env python 
#encoding=utf-8
import mylib
from sys import argv
from mylib import myargv

if __name__ == '__main__':
    myargv = myargv(argv)
    if myargv.parse_cmdline() == 0:
        pass
    else:
        mylib.get_innodb_page_type(myargv)

mylib.py

View Code
encoding=utf-8
import os
import include
from include import *

TABLESPACE_NAME='D:\\mysql_data\\test\\t.ibd'
VARIABLE_FIELD_COUNT = 1
NULL_FIELD_COUNT = 0

class myargv(object):
    def __init__(self, argv):
        self.argv = argv
        self.parms = {}
        self.tablespace = ''

    def parse_cmdline(self):
        argv = self.argv
        if len(argv) == 1:
            print 'Usage: python py_innodb_page_info.py [OPTIONS] tablespace_file'
            print 'For more options, use python py_innodb_page_info.py -h'
            return 0
        while argv:
            if argv[0][0] == '-':
                if argv[0][1] == 'h':
                    self.parms[argv[0]] = ''
                    argv = argv[1:]
                    break
                if argv[0][1] == 'v':
                    self.parms[argv[0]] = ''
                    argv = argv[1:]
                else:
                    self.parms[argv[0]] = argv[1]
                    argv = argv[2:]
            else:
                self.tablespace = argv[0]
                argv = argv[1:]
        if self.parms.has_key('-h'):
            print 'Get InnoDB Page Info'
            print 'Usage: python py_innodb_page_info.py [OPTIONS] tablespace_file\n'
            print 'The following options may be given as the first argument:'
            print '-h        help '
            print '-o output put the result to file'
            print '-t number thread to anayle the tablespace file'
            print '-v        verbose mode'
            return 0
        return 1

def mach_read_from_n(page,start_offset,length):
    ret = page[start_offset:start_offset+length]
    return ret.encode('hex')

def get_innodb_page_type(myargv):
    f=file(myargv.tablespace,'rb')
    fsize = os.path.getsize(f.name)/INNODB_PAGE_SIZE
    ret = {}
    for i in range(fsize):
        page = f.read(INNODB_PAGE_SIZE)
        page_offset = mach_read_from_n(page,FIL_PAGE_OFFSET,4)
        page_type = mach_read_from_n(page,FIL_PAGE_TYPE,2)
        if myargv.parms.has_key('-v'):
            if page_type == '45bf':
                page_level = mach_read_from_n(page,FIL_PAGE_DATA+PAGE_LEVEL,2)
                print "page offset %s, page type <%s>, page level <%s>"%(page_offset,innodb_page_type[page_type],page_level)
            else:
                print "page offset %s, page type <%s>"%(page_offset,innodb_page_type[page_type])
        if not ret.has_key(page_type):
            ret[page_type] = 1
        else:
            ret[page_type] = ret[page_type] + 1
    print "Total number of page: %d:"%fsize
    for type in ret:
        print "%s: %s"%(innodb_page_type[type],ret[type])

include.py

View Code
#encoding=utf-8
INNODB_PAGE_SIZE = 16*1024*1024

# Start of the data on the page
FIL_PAGE_DATA = 38


FIL_PAGE_OFFSET = 4 # page offset inside space
FIL_PAGE_TYPE = 24 # File page type

# Types of an undo log segment */
TRX_UNDO_INSERT = 1
TRX_UNDO_UPDATE = 2

# On a page of any file segment, data may be put starting from this offset
FSEG_PAGE_DATA = FIL_PAGE_DATA

# The offset of the undo log page header on pages of the undo log
TRX_UNDO_PAGE_HDR = FSEG_PAGE_DATA

PAGE_LEVEL = 26 #level of the node in an index tree; the leaf level is the level 0 */

innodb_page_type={
    '0000':u'Freshly Allocated Page',
    '0002':u'Undo Log Page',
    '0003':u'File Segment inode',
    '0004':u'Insert Buffer Free List',
    '0005':u'Insert Buffer Bitmap',
    '0006':u'System Page',
    '0007':u'Transaction system Page',
    '0008':u'File Space Header',
    '0009':u'擴充套件描述頁',
    '000a':u'Uncompressed BLOB Page',
    '000b':u'1st compressed BLOB Page',
    '000c':u'Subsequent compressed BLOB Page',
    '45bf':u'B-tree Node'
    }

innodb_page_direction={
    '0000': 'Unknown(0x0000)',
    '0001': 'Page Left',
    '0002': 'Page Right',
    '0003': 'Page Same Rec',
    '0004': 'Page Same Page',
    '0005': 'Page No Direction',
    'ffff': 'Unkown2(0xffff)'
}


INNODB_PAGE_SIZE=1024*16 # InnoDB Page 16K

測試1:

root@localhost : test 02:26:13>create table tt(id int auto_increment,name varchar(10),age int,address varchar(20),primary key (id))engine=innodb;
Query OK, 0 rows affected (0.17 sec)
root@zhoujy:/var/lib/mysql/test# ls -lh tt.ibd 
-rw-rw---- 1 mysql mysql 96K 2012-10-17 14:26 tt.ibd

檢視ibd:

root@zhoujy:/home/zhoujy/jiaoben/read_ibd# python py_innodb_page_info.py /var/lib/mysql/test/tt.ibd -v
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0000> ---葉子節點
page offset 00000000, page type <Freshly Allocated Page>
page offset 00000000, page type <Freshly Allocated Page>
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

解釋:
Total number of page: 總頁數
Freshly Allocated Page:可用頁
Insert Buffer Bitmap:插入快取點陣圖頁
Insert Buffer Free List:插入快取空閒列表頁
B-tree Node:資料頁

Uncompressed BLOB Page:二進位制大物件頁,存放溢位行的頁,即溢位頁
上面得到的資訊是表初始化大小為96K,他是有 Total number of page * 16 得來的。1個資料頁,2個可用頁面。

root@localhost : test 02:42:58>insert into tt values(name,age,address) values('aaa',23,'HZZZ');

疑惑:為什麼沒有申請區?區是64個連續的頁,大小1M。那麼表大小也應該是至少1M。但是現在只有96K(預設)。原因是因為每個段開始的時候,先有32個頁大小的碎片頁存放資料,使用
完之後才是64頁的連續申請,最多每次可以申請4個區,保證資料的順序。這裡看出表大小增加是按照至少64頁的大小的空間來增加的,即1M增加。
驗證:
填充資料,寫滿這32個碎片頁,32*16 = 512K。看看是否能申請大於1M的空間。

View Code
root@zhoujy:/home/zhoujy/jiaoben/read_ibd# ls -lh /var/lib/mysql/test/tt.ibd 
-rw-rw---- 1 mysql mysql 576K 2012-10-17 15:30 /var/lib/mysql/test/tt.ibd
root@zhoujy:/home/zhoujy/jiaoben/read_ibd# python py_innodb_page_info.py /var/lib/mysql/test/tt.ibd -v
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0001>
page offset 00000004, page type <B-tree Node>, page level <0000>
page offset 00000005, page type <B-tree Node>, page level <0000>
page offset 00000006, page type <B-tree Node>, page level <0000>
page offset 00000007, page type <B-tree Node>, page level <0000>
page offset 00000008, page type <B-tree Node>, page level <0000>
page offset 00000009, page type <B-tree Node>, page level <0000>
page offset 0000000a, page type <B-tree Node>, page level <0000>
page offset 0000000b, page type <B-tree Node>, page level <0000>
page offset 0000000c, page type <B-tree Node>, page level <0000>
page offset 0000000d, page type <B-tree Node>, page level <0000>
page offset 0000000e, page type <B-tree Node>, page level <0000>
page offset 0000000f, page type <B-tree Node>, page level <0000>
page offset 00000010, page type <B-tree Node>, page level <0000>
page offset 00000011, page type <B-tree Node>, page level <0000>
page offset 00000012, page type <B-tree Node>, page level <0000>
page offset 00000013, page type <B-tree Node>, page level <0000>
page offset 00000014, page type <B-tree Node>, page level <0000>
page offset 00000015, page type <B-tree Node>, page level <0000>
page offset 00000016, page type <B-tree Node>, page level <0000>
page offset 00000017, page type <B-tree Node>, page level <0000>
page offset 00000018, page type <B-tree Node>, page level <0000>
page offset 00000019, page type <B-tree Node>, page level <0000>
page offset 0000001a, page type <B-tree Node>, page level <0000>
page offset 0000001b, page type <B-tree Node>, page level <0000>
page offset 0000001c, page type <B-tree Node>, page level <0000>
page offset 0000001d, page type <B-tree Node>, page level <0000>
page offset 0000001e, page type <B-tree Node>, page level <0000>
page offset 0000001f, page type <B-tree Node>, page level <0000>
page offset 00000020, page type <B-tree Node>, page level <0000>
page offset 00000021, page type <B-tree Node>, page level <0000>
page offset 00000022, page type <B-tree Node>, page level <0000>
page offset 00000023, page type <B-tree Node>, page level <0000>
Total number of page: 36:
Insert Buffer Bitmap: 1
File Space Header: 1
B-tree Node: 33
File Segment inode: 1

"額外"頁:4個
page offset 00000000, page type <File Space Header> :檔案頭空間頁
page offset 00000001, page type <Insert Buffer Bitmap>:插入快取點陣圖頁
page offset 00000002, page type <File Segment inode>:檔案段節點
page offset 00000003, page type <B-tree Node>, page level <0001>:根頁
碎片頁:32個
page type <B-tree Node>, page level <0000>
總共36個頁,ibd大小 576K的由來:32*16=512K(碎片頁)+ 4*16=64(額外頁),這裡開始要是再插入的話,應該申請最少1M的頁:

root@zhoujy:/home/zhoujy/jiaoben/read_ibd# ls -lh /var/lib/mysql/test/tt.ibd 
-rw-rw---- 1 mysql mysql 2.0M 2012-10-17 16:10 /var/lib/mysql/test/tt.ibd
root@zhoujy:/home/zhoujy/jiaoben/read_ibd# python py_innodb_page_info.py /var/lib/mysql/test/tt.ibd
Total number of page: 128:
Freshly Allocated Page: 91
Insert Buffer Bitmap: 1
File Space Header: 1
B-tree Node: 34
File Segment inode: 1

頁從36跳到了128,因為已經用完了32個碎片頁,新的頁會採用區的方式進行空間申請。資訊中看到有很多可用頁,正好說明這點。

 ▲溢位行資料存放:INNODB儲存引擎是索引組織的,即每頁中至少有兩行記錄,因此如果頁中只能存放一行記錄,INNODB會自動將行資料放到溢位頁中。當發生溢位行的時候,實際資料儲存在BLOB頁中,資料頁只儲存資料的前768位元組(老的檔案格式),新的檔案格式(Barracuda)採用完全行溢位的方式,資料頁只儲存20個位元組的指標,BLOB也儲存所有資料。如何檢視錶中有溢位行資料呢?

root@localhost : test 04:52:34>create table t1 (id int,name varchar(10),memo varchar(8000))engine =innodb default charset utf8;
Query OK, 0 rows affected (0.16 sec)

root@localhost : test 04:53:10>insert into t1 values(1,'zjy',repeat('',8000));
Query OK, 1 row affected (0.00 sec)

 檢視ibd:

root@zhoujy:/home/zhoujy/jiaoben/read_ibd# python py_innodb_page_info.py /var/lib/mysql/test/t1.ibd -v
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0000>
page offset 00000004, page type <Uncompressed BLOB Page>
page offset 00000005, page type <Uncompressed BLOB Page>
Total number of page: 6:
Insert Buffer Bitmap: 1
Uncompressed BLOB Page: 2
File Space Header: 1
B-tree Node: 1
File Segment inode: 1

從資訊中看到,剛才插入的一行記錄,已經溢位了,儲存到了2個BLOB頁中(<Uncompressed BLOB Page>)。因為1頁只有16K,又要存2行資料,所以每行記錄最好小於8K,而上面的遠遠大於8K,所以被溢位了。當然這個也不是包括特大欄位,要是一張表裡面有5個欄位都是varchar(512)【多個varchar的總和大於8K就可以】,也會溢位:

root@localhost : test 05:08:39>create table t2 (id int,name varchar(1000),address varchar(512),company varchar(200),xx varchar(512),memo varchar(512),dem varchar(1000))engine =innodb default charset utf8;
Query OK, 0 rows affected (0.17 sec)
root@localhost : test 05:08:43>insert into t2 values(1,repeat('',1000),repeat('',500),repeat('',500),repeat('',500),repeat('',500),repeat('阿a',500));

1000+500+500+500+500+500=3500*3>8000位元組;行會被溢位:

root@zhoujy:/home/zhoujy/jiaoben/read_ibd# python py_innodb_page_info.py /var/lib/mysql/test/t2.ibd -v
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0000>
page offset 00000004, page type <Uncompressed BLOB Page>
page offset 00000000, page type <Freshly Allocated Page>
Total number of page: 6:
Insert Buffer Bitmap: 1
Freshly Allocated Page: 1
File Segment inode: 1
B-tree Node: 1
File Space Header: 1
Uncompressed BLOB Page: 1

<Uncompressed BLOB Page> 頁存放真正的資料,那資料頁到底存放什麼?用hexdump檢視:

root@zhoujy:/home/zhoujy/jiaoben/read_ibd# hexdump -C -v  /var/lib/mysql/test/t1.ibd  > t1.txt

檢視ibd:

View Code
3082 0000c090  00 32 01 10 80 00 00 01  7a 6a 79 e6 88 91 e6 88  |.2......zjy.....|
3083 0000c0a0  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3084 0000c0b0  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3085 0000c0c0  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3086 0000c0d0  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3087 0000c0e0  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3088 0000c0f0  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3089 0000c100  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3090 0000c110  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3091 0000c120  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3092 0000c130  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3093 0000c140  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3094 0000c150  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3095 0000c160  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3096 0000c170  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3097 0000c180  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3098 0000c190  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3099 0000c1a0  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3100 0000c1b0  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3101 0000c1c0  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3102 0000c1d0  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3103 0000c1e0  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3104 0000c1f0  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3105 0000c200  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3106 0000c210  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3107 0000c220  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3108 0000c230  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3109 0000c240  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3110 0000c250  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3111 0000c260  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3112 0000c270  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3113 0000c280  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3114 0000c290  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3115 0000c2a0  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3116 0000c2b0  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3117 0000c2c0  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3118 0000c2d0  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3119 0000c2e0  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3120 0000c2f0  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3121 0000c300  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3122 0000c310  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3123 0000c320  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3124 0000c330  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3125 0000c340  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3126 0000c350  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3127 0000c360  88 91 e6 88 91 e6 88 91  e6 88 91 e6 88 91 e6 88  |................|
3128 0000c370  91 e6 88 91 e6 88 91 e6  88 91 e6 88 91 e6 88 91  |................|
3129 0000c380  e6 88 91 e6 88 91 e6 88  91 e6 88 91 e6 88 91 e6  |................|
3130 0000c390  88 91 e6 88 91 e6 88 91  e6 88 91 00 00 02 1c 00  |................|

文字中剛好是48行,每行16位元組。48*16=768位元組,剛好驗證了之前說的:資料頁只儲存資料的前768位元組(老的檔案格式)。

總結1:
     通過上面的資訊,可以能清楚的知道ibd表空間各個頁的分佈和利用資訊以及表空間大小增加的步長;特別注意的是溢位行,一個頁中至少包含2行資料,如果頁中存放的行數越多,效能就越好。

************************************
************************************
目的2:
     
瞭解表空間如何儲存資料,以及對NULL值的儲存。

測試2:
在測試前先了解INNODB的儲存格式(row_format)。老格式(Antelope):Compact<預設>,Redumdant;新格式(Barracuda):Compressed ,Dynamic。

這裡測試指標對預設的儲存格式。
Compact行記錄方式如下:

   |變長欄位長度列表(1~2位元組)|NULL標誌位(1位元組)|記錄頭資訊(5位元組)|RowID(6位元組)|事務ID(6位元組)|回滾指標(7位元組)|

上面資訊除了 "NULL標誌位"[表中所有欄位都定義為NOT NULL],"RowID"[表中有主鍵] ,"變長欄位長度列表" [沒有變長欄位] 可能不存在外,其他資訊都會出現。所以一行資料除了列資料所佔用的欄位外,還需要額外18位元組。

一:欄位全NULL

mysql> create table mytest(t1 varchar(10),t2 varchar(10),t3 varchar(10) ,t4 varchar(10))engine=innodb charset = latin1 row_format=compact;
Query OK, 0 rows affected (0.08 sec)

mysql> insert into mytest values('a','bb','bb','ccc');
Query OK, 1 row affected (0.02 sec)

mysql> insert into mytest values('a','ee','ee','fff');
Query OK, 1 row affected (0.01 sec)

mysql> insert into mytest values('a',NULL,NULL,'fff');
Query OK, 1 row affected (0.00 sec)

測試資料準備完之後,執行shell命令:

root@zhoujy:/usr/local/mysql/test# hexdump -C -v mytest.ibd > /home/zhoujy/mytest.txt

開啟mytest.txt檔案找到supremum這一行:

0000c070  73 75 70 72 65 6d 75 6d  03 02 02 01 00 00 00 10  |supremum........|   ----------->一行,16位元組
0000c080  00 25 00 00 00 03 b9 00  00 00 00 02 49 01 82 00  |.%..........I...|
0000c090  00 01 4a 01 10 61 62 62  62 62 63 63 63 03 02 02  |..J..abbbbccc...|
0000c0a0  01 00 00 00 18 00 23 00  00 00 03 b9 01 00 00 00  |......#.........|
0000c0b0  02 49 02 83 00 00 01 4b  01 10 61 65 65 65 65 66  |.I.....K..aeeeef|
0000c0c0  66 66 03 01 06 00 00 20  ff a6 00 00 00 03 b9 02  |ff..... ........|
0000c0d0  00 00 00 02 49 03 84 00  00 01 4c 01 10 61 66 66  |....I.....L..aff|
0000c0e0  66 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |f...............|

釋:
第一行資料:
03 02 02 01 /*變長欄位*/ ---- 表中4個欄位型別為varchar,並且沒有NULL資料,而且每個欄位君小於255。
00 /*NULL標誌位,第一行沒有null的資料*/
00 00 10 00 25 /*記錄頭資訊,固定5個位元組*/
00 00 00 03 b9 00 /*RowID,固定6個位元組,表沒有主鍵*/
00 00 00 02 49 01 /*事務ID,固定6個位元組*/
82 00 00 01 4a 01 10 /*回滾指標,固定7個位元組*/
61 62 62 62 62 63 63 63 /*列的資料*/
第二行資料和第一行資料一樣(顏色匹配)。
第三行資料(有NULL值)和第一行的解釋的顏色對應起來比較差別:

03 02 02 01  VS  03 01   ----------當值為NULL時,變長欄位列表不會佔用儲存空間。
61 62 62  62 62 63 63 63 VS 61 66 66 66  --------- NULL值沒有儲存,不佔空間

結論:當值為NULL時,變長欄位列表不會佔用儲存空間。NULL值沒有儲存,不佔空間,但是需要一個標誌位(一行一個)。

二:欄位全NOT NULL

mysql> create table mytest(t1 varchar(10) NOT NULL,t2 varchar(10) NOT NULL,t3 varchar(10) NOT NULL,t4 varchar(10) NOT NULL)engine=innodb charset = latin1 row_format=compact;
Query OK, 0 rows affected (0.03 sec)

mysql> insert into mytest values('a','bb','bb','ccc');
Query OK, 1 row affected (0.01 sec)

mysql> insert into mytest values('a','ee','ee','fff');
Query OK, 1 row affected (0.01 sec)

mysql> insert into mytest values('a',NULL,NULL,'fff');
ERROR 1048 (23000): Column 't2' cannot be null

 步驟和上面一樣,得到的ibd的結果是:

0000c070  73 75 70 72 65 6d 75 6d  03 02 02 01 00 00 10 00  |supremum........|
0000c080  24 00 00 00 03 b9 03 00  00 00 02 49 07 87 00 00  |$..........I....|
0000c090  01 4f 01 10 61 62 62 62  62 63 63 63 03 02 02 01  |.O..abbbbccc....|
0000c0a0  00 00 18 ff cb 00 00 00  03 b9 04 00 00 00 02 49  |...............I|
0000c0b0  08 88 00 00 01 50 01 10  61 65 65 65 65 66 66 66  |.....P..aeeeefff|

和上面比較,發現少了NULL的標誌位資訊。
結論:  NULL值會有額外的空間來儲存,即每行1位元組的大小。對於相同資料的表,欄位中有NULL值的表比NOT NULL的大。

三:1個NULL,和1個''的資料:

mysql> create table mytest(t1 varchar(10) NOT NULL,t2 varchar(10) NOT NULL DEFAULT '',t3 varchar(10) NOT NULL ,t4 varchar(10))engine=innodb charset = latin1 row_format=compact;
Query OK, 0 rows affected (0.02 sec)
mysql> insert into mytest(t1,t2) values('A','BB');
Query OK, 1 row affected, 1 warning (0.01 sec)

 步驟和上面一樣,得到的ibd的結果是:

0000c070  73 75 70 72 65 6d 75 6d  00 02 01 01 00 00 10 ff  |supremum........|
0000c080  ef 00 00 00 43 b9 03 00  00 00 02 4a 15 90 00 00  |....C......J....|
0000c090  01 c2 01 10 41 42 42 00  00 00 00 00 00 00 00 00  |....ABB.........|

和上面2個區別主要在於變長列表和列資料這裡。

結論:列資料資訊裡表明了 NULL資料和''資料都不佔用任何空間,對於變長欄位列表的資訊,和一對比得出:‘’資料雖然不需要佔用任何儲存空間,但是在變長欄位列表裡面還是需要佔用一個位元組<畢竟還是一個‘’值>,NULL值不需要佔用”,只是NULL會有額外的一個標誌位,所以能有個優化的說法:“資料庫表中能設定NOT NULL的就儘量設定為NOT NULL,除非確實需要NULL值得。” 在此得到了證明。

上面的測試都是針對VARCHAR的變長型別,那對於CHAR呢?

CHAR 測試:

root@localhost : test 10:33:35>create table mytest(t1 char(10),t2 char(10),t3 char(10) ,t4 char(10))engine=innodb charset = latin1 row_format=compact;Query OK, 0 rows affected (0.16 sec)

root@localhost : test 10:33:59>insert into mytest values('a','bb','bb','ccc');
Query OK, 1 row affected (0.00 sec)

root@localhost : test 10:34:09>insert into mytest values('a','ee','ee','fff');
Query OK, 1 row affected (0.00 sec)

root@localhost : test 10:34:19>insert into mytest values('a',NULL,NULL,'fff');
Query OK, 1 row affected (0.00 sec)

開啟ibd生成的檔案:

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 41 00 00  |supremum.....A..|
0000c080  00 0a f5 00 00 00 00 81  2d 07 80 00 00 00 32 01  |........-.....2.|
0000c090  10 61 20 20 20 20 20 20  20 20 20 62 62 20 20 20  |.a         bb   |
0000c0a0  20 20 20 20 20 62 62 20  20 20 20 20 20 20 20 63  |     bb        c|
0000c0b0  63 63 20 20 20 20 20 20  20 00 00 00 18 00 41 00  |cc       .....A.|
0000c0c0  00 00 0a f5 01 00 00 00  81 2d 08 80 00 00 00 32  |.........-.....2|
0000c0d0  01 10 61 20 20 20 20 20  20 20 20 20 65 65 20 20  |..a         ee  |
0000c0e0  20 20 20 20 20 20 65 65  20 20 20 20 20 20 20 20  |      ee        |
0000c0f0  66 66 66 20 20 20 20 20  20 20 06 00 00 20 ff 70  |fff       ... .p|
0000c100  00 00 00 0a f5 02 00 00  00 81 2d 09 80 00 00 00  |..........-.....|
0000c110  32 01 10 61 20 20 20 20  20 20 20 20 20 66 66 66  |2..a         fff|
0000c120  20 20 20 20 20 20 20 00  00 00 00 00 00 00 00 00  |       .........|

和一的varchar比較發現:少了變長欄位列表,但是對於char來講,需要固定長度來儲存的,存不到固定長度,也會被填充滿。如:20;並且NULL值也不需要佔用儲存空間。

混合(varchar,char):

root@localhost : test 11:21:48>create table mytest(t1 int,t2 char(10),t3 varchar(10) ,t4 char(10))engine=innodb charset = latin1 row_format=compact;
Query OK, 0 rows affected (0.17 sec)

root@localhost : test 11:21:50>insert into mytest values(1,'a','b','c');
Query OK, 1 row affected (0.00 sec)

root@localhost : test 11:22:06>insert into mytest values(11,'aa','bb','cc');
Query OK, 1 row affected (0.00 sec)

 從上面的表結構中看出:
1,變長欄位列表長度:1
2,NULL標誌位:1
3,記錄頭資訊:5
4,RowID:6
5,事務ID:6
6,回滾指標:7

idb的資訊

0000c070  73 75 70 72 65 6d 75 6d  01 00 00 00 10 00 33 00  |supremum......3.| 
0000c080  00 00 0a f5 07 00 00 00  81 2d 1a 80 00 00 00 32  |.........-.....2|
0000c090  01 10 80 00 00 01 61 20  20 20 20 20 20 20 20 20  |......a         |
0000c0a0  62 63 20 20 20 20 20 20  20 20 20 02 00 00 00 18  |bc         .....|
0000c0b0  ff be 00 00 00 0a f5 08  00 00 00 81 2d 1b 80 00  |............-...|
0000c0c0  00 00 32 01 10 80 00 00  0b 61 61 20 20 20 20 20  |..2......aa     |
0000c0d0  20 20 20 62 62 63 63 20  20 20 20 20 20 20 20 00  |   bbcc        .|

從上資訊得出和之前預料的一樣:因為表中只有一個varchar欄位,所以,變長列表長度就只有:01 
特別注意的是:各個列資料儲存的資訊:t1欄位為int 型別,佔用4個位元組的大小。第一行:80 00 00 01 就是表示 1 數字;第二行:80 00 00   0b 表示了11的數字。[select hex(11)  == B ],其他的和上面的例子一樣。

上面都是latin1單位元組字符集的說明,那對於多位元組字符集的情況怎麼樣?

root@localhost : test 11:52:10>create table mytest(id int auto_increment,t2 varchar(10),t3 varchar(10) ,t4 char(10),primary key(id))engine=innodb charset = utf8 row_format=compact;
Query OK, 0 rows affected (0.17 sec)

root@localhost : test 11:52:11>insert into mytest(t2,t3,t4) values('bb','bb','ccc');
Query OK, 1 row affected (0.00 sec)

root@localhost : test 11:55:34>insert into mytest(t2,t3,t4) values('我們','他們','我們的');
Query OK, 1 row affected (0.00 sec)

 ibd資訊如下:

0000c070  73 75 70 72 65 6d 75 6d  0a 02 02 00 00 00 10 00  |supremum........|
0000c080  28 80 00 00 01 00 00 00  81 2d 27 80 00 00 00 32  |(........-'....2|
0000c090  01 10 62 62 62 62 63 63  63 20 20 20 20 20 20 20  |..bbbbccc       |
0000c0a0  0a 06 06 00 00 00 18 ff  c7 80 00 00 02 00 00 00  |................|
0000c0b0  81 2d 28 80 00 00 00 32  01 10 e6 88 91 e4 bb ac  |.-(....2........|
0000c0c0  e4 bb 96 e4 bb ac e6 88  91 e4 bb ac e7 9a 84 20  |............... |

因為表有了主鍵,所以ROWID(6位元組)不見了。
特別注意的是:變長欄位列表是3?表裡面的varchar型別的列只有2個啊。經測試得出:在多位元組字符集的條件下,char型別被當成可變長度的型別來處理,他們的行儲存基本沒有區別,所以這個就出現變長列表是3了,因為是utf8字符集,佔用三個位元組。所以一個漢字均佔用了一個頁中3個位元組的空間(”我們“ :e6 88 91 e4 bb ac)。
資料列的資訊:
id列的1值,應該是
80 00 00 01,為什麼這個顯示00 32 01 10,而且所有的id都是00 32 01 10。測試發現,id為自增主鍵的時候,id的4個位元組長度都是以00 32 01 10 表示。否則和前面一個例子裡說的,用select HEX(X) 表示。

總結2:
     上面的測試都是基於COMPACT儲存格式的,不管是varchar還是char,NULL值是不需要佔用儲存空間的;特別需要注意的是Redumdant的記錄頭資訊需要6個固定位元組;在多位元組字符集的條件下,CHAR和VARCHAR的行儲存基本是沒有區別的

相關文章