国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發(fā) > 綜合 > 正文

解剖SQLSERVER 第十四篇 Vardecimals 存儲格式揭秘(譯)

2024-07-21 02:48:56
字體:
供稿:網(wǎng)友
解剖SQLSERVER 第十四篇 Vardecimals 存儲格式揭秘(譯)解剖SQLSERVER 第十四篇 Vardecimals存儲格式揭秘(譯)

http://imPRove.dk/how-are-vardecimals-stored/

在這篇文章,我將深入研究vardecimals 是怎麼存儲在磁盤上的。

作為一般的介紹vardecimals 是怎樣的,什么時(shí)候應(yīng)該使用,怎樣使用,參考這篇文章

vardecimal 存儲格式啟用了嗎?

首先,我們需要看一下vardecimals 是否已經(jīng)開啟了,因?yàn)樗麜耆淖僤ecimals 的存儲方式。Vardecimal 不是獨(dú)立的一種數(shù)據(jù)類型,所有使用decimals 的列都會使用vardecimals方式來存儲并使用相同的system type(106)。注意在SQLSERVER里面,numeric 跟decimal是完全一樣的。無論我在哪里我提到decimal, 你都可以使用numeric來替代并且會得到相同的結(jié)果

你可以執(zhí)行下面語句來查看給定的表的vardecimal 是否開啟

SELECT OBJECTPROPERTY(OBJECT_ID('MyTable'), 'TableHasVarDecimalStorageFormat')

如果你沒有權(quán)限運(yùn)行上面語句,或者不想使用OBJECTPROPERTY函數(shù),你可以查詢sys.system_internals_partition_columns DMV 獲取同樣的信息。

USE testGOSELECT    COUNT(*)FROM    sys.system_internals_partition_columns PCINNER JOIN    sys.partitions P ON P.partition_id = pc.partition_idINNER JOIN    sys.tables T ON T.object_id = P.object_idWHERE    T.name = 'test_vardecimal' AND    P.index_id <= 1 AND    PC.system_type_id = 106 AND    PC.leaf_offset < 0

固定長度變?yōu)榭勺冮L度正常的decimal列在記錄里面使用固定長度來存儲。這意味著存儲的是真正的數(shù)據(jù)。他不需要保存存儲的字節(jié)數(shù)的長度信息這個(gè)長度信息用來計(jì)算并存儲在元數(shù)據(jù)里。

一旦你打開vardecimals, 所有的decimals 不再使用固定長度來存儲,進(jìn)而用可變長度代替。

將decimal 作為可變長度字段來存儲有一些特別含義1、我們再也不能使用靜態(tài)的方法來計(jì)算一個(gè)給定的值的所需字節(jié)數(shù)2、會有兩個(gè)字節(jié)的開銷用來存儲偏移值在可變長度偏移數(shù)組里3、如果先前行記錄沒有可變長度列,那么開銷實(shí)際上是4個(gè)字節(jié)因?yàn)槲覀円残枰鎯勺冮L度列的列數(shù)量4、decimal 的實(shí)際值變成了可變數(shù)量的字節(jié) 這需要我們?nèi)ソ庾x

vardecimal 值由哪些部分組成一旦我們開始解析行記錄然后在可變長度部分檢索vardecimal 的值,我們需要解析里面的數(shù)據(jù)。

正常的decimals 基本能存儲一個(gè)巨大無比的整數(shù)(范圍是根據(jù)元數(shù)據(jù)定義的decimal的位置)vardecimals 的存儲使用科學(xué)計(jì)數(shù)法。使用科學(xué)計(jì)數(shù)法,我們需要存儲三個(gè)不同的值1、符號(正數(shù)/負(fù)數(shù))2、指數(shù)3、(對數(shù)的) 尾數(shù)

使用這三個(gè)組成部分,我們可以使用下面的公式計(jì)算出實(shí)際值

(sign) * mantissa * 10<sup>exponent</sup>

例子

假設(shè)我們有一個(gè)vardecimal(5,2)列,我們存儲的值是123.45。在科學(xué)記數(shù)法里,將表示為1.2345 * 102。在這種情況下我們有正數(shù)符號(1),一個(gè)尾數(shù)1.2345和一個(gè)指數(shù)2。SQL Server知道尾數(shù)總是有一個(gè)固定小數(shù)點(diǎn)在第一個(gè)數(shù)字后面,正因?yàn)槿绱耍@會簡單的存儲整數(shù)值12345作為尾數(shù),

當(dāng)指數(shù)是2,SQLSERVER知道我們的范圍由指數(shù)2來定義,數(shù)值向右移動(dòng)指數(shù)的長度,存儲0作為實(shí)際的指數(shù)

一旦我們讀取這個(gè)數(shù),我使用下面的公式計(jì)算尾數(shù)(注意我們在這里不關(guān)心如果尾數(shù)是正的還是負(fù)的--我們會稍后將他保存到account里)

mantissa / 10<sup>floor(log10(mantissa))</sup>

將我們的值帶入進(jìn)去,我們得到

12345 / 10<sup>floor(log10(12345))</sup>

通過簡化我們得到

12345 / 10<sup>4</sup>

使用科學(xué)計(jì)數(shù)法得到最終的尾數(shù)

1.2345

到目前為止一切順利,我們現(xiàn)在得到尾數(shù)的值。在這里我們需要做兩件事1、加上符號位2、根據(jù)指數(shù)值將decimal的小數(shù)點(diǎn)移動(dòng)到右邊正確的位置。當(dāng)SQLSERVER知道范圍是2,他將2替代4,并減去指數(shù)指定的范圍--允許我們忽略范圍并且只需要直接計(jì)算數(shù)字

因此我們獲得了我們最終需要計(jì)算的最后數(shù)字

(sign) * mantissa * 10<sup>exponent</sup> => (1) * 1.2345 * 10<sup>2</sup> => 1.2345 * 10<sup>2</sup> = 123.45

讀取符號位和指數(shù)第一個(gè)字節(jié)包含符號和指數(shù)。在先前的例子里面,這些值占用4個(gè)字節(jié)(包含額外的2個(gè)字節(jié)的偏移數(shù)組)

0xC21EDC20

如果我們觀察第一個(gè)字節(jié),并且將他轉(zhuǎn)換為binary,我們獲得下面信息

最重要的那個(gè)位是最左邊的那個(gè)位,或者從右開始數(shù)第7位(位置編號從0開始)那個(gè)位是符號位。如果設(shè)置為1表示正數(shù),如果為0表示負(fù)數(shù),我們看到是1表示正數(shù)

位0 - 6是一個(gè)7位的包含指數(shù)的值。一個(gè)常規(guī)的無符號7位值可以包含的范圍是0~127。當(dāng)decimal 數(shù)據(jù)類型需要表示一個(gè)范圍 –1038+1到 1038-1,我們就需要存儲負(fù)數(shù)。

我們可以使用7位的其中一位作為符號位,在其余的6位里存儲值,允許的范圍是–64 到 63。然而SQLSERVER會用盡7位去存儲本身的數(shù)值,

不過存儲的是一個(gè)偏移值64。因此,指數(shù)0會被存儲為64 (64-64=0)。指數(shù)-1 會存儲63 (63-64=-1),指數(shù)1會存儲65 (65-64=1)如此類推。

在我們的例子里,讀取位0-6 將會得到下面的值

66減去64這個(gè)偏移值就得出 指數(shù)266-64=2(指數(shù))

尾數(shù)的chunk存儲余下的字節(jié)包含尾數(shù)值。我們把他轉(zhuǎn)換為二進(jìn)制

Hex: 1E       DC       20Bin: 00011110 11011100 00100000

尾數(shù)存儲在10位的chunks里,每一個(gè)chunk顯式尾數(shù)的3個(gè)數(shù)字(記住,尾數(shù)是一個(gè)很大的整數(shù),直到后來我們開始把他作為一個(gè)十進(jìn)制的指針數(shù)值 10的n次冪)

把這些字節(jié)切割放進(jìn)去chunks里面 會得到如下的組別

在這種情況下,一個(gè)字節(jié)8位 SQLSERVER在這里會浪費(fèi)4位使用這種chunk大小的話。問題來了,為什么要選擇一個(gè)chunk大小為10位?那10位 需要顯示所有可能的三位整數(shù)(0-999)。

如果我們使用一個(gè)chunk的大小來表示一個(gè)數(shù)字會怎樣?

在這種情況下,一個(gè)字節(jié)8位 SQLSERVER在這里會浪費(fèi)4位使用這種chunk大小的話。問題來了,為什么要選擇一個(gè)chunk大小為10位?那10位 需要顯示所有可能的三位整數(shù)(0-999)。如果我們使用一個(gè)chunk的大小來表示一個(gè)數(shù)字會怎樣?

剛才那樣的情況,我們需要展示數(shù)值0-9.那總共需要4個(gè)位(0b1001 = 9)。然而,使用4個(gè)位的時(shí)候我們實(shí)際可以展示的最大范圍是0-15(0b1111 = 15) --意味著我們浪費(fèi)了6個(gè)值的空間(15-9=6)這些值永遠(yuǎn)不需要的。從百分比來講,我們浪費(fèi)了6/16=37.5%

讓我們試著畫出不同的chunk大小對應(yīng)浪費(fèi)的百分比的圖:

我們看到chunk大小選擇4和選擇7 相比起選擇chunk大小為10 有較大的浪費(fèi)。在chunk大小為20時(shí),對于0浪費(fèi)相當(dāng)接近,但是他還是有2倍浪費(fèi)率相比起10來說

現(xiàn)在,浪費(fèi)不是最重要的。對于壓縮來說,最理想的情況是對于絕對必要的數(shù)字我們不想使用多余的數(shù)字。在chunk大小為10的情況下,可以顯示3個(gè)數(shù)字,我們浪費(fèi)了2個(gè)數(shù)字空間范圍是0-9。

然而,我們只關(guān)注范圍100-999。如果我們的chunk大小選擇20個(gè)位,每個(gè)chunk顯示6個(gè)數(shù)字,我們浪費(fèi)了了一些字節(jié) 值從0-99999,當(dāng)我們只關(guān)注值1000000-999999。

基本上,這是一個(gè)折衷方案,浪費(fèi)就越少 而且也越好。我們繼續(xù)看圖表,粒度越來越少。很明顯 選擇10個(gè)位作為chunk的大小是最好的選擇 --這個(gè)選擇的浪費(fèi)是最小的 并且有合適的粒度大小 3個(gè)數(shù)字

在我們繼續(xù)之前還有一些細(xì)節(jié)。想象一下我們需要存儲的尾數(shù)值為4.12,有效的整數(shù)值是412

Dec: 412Bin: 01100111 00Hex: 67       0

在這種情況下,我們會浪費(fèi)8位在第二個(gè)字節(jié),因?yàn)槲覀冎恍枰粋€(gè)塊,但我們需要兩個(gè)字節(jié)來表示這10位。在這種情況下,鑒于過去兩位不設(shè)置,SQL Server會截?cái)嘧詈笠粋€(gè)字節(jié)。因此,如果你正在讀一塊,你的磁盤上,你可以假設(shè)其余部分不設(shè)置。

在這種情況下,我在第二個(gè)字節(jié)浪費(fèi)8個(gè)位,因?yàn)槲覀冎恍枰粋€(gè)chunk,不過我們需要兩個(gè)字節(jié)來顯示這個(gè)位。在這種情況下,多余的兩個(gè)位不會進(jìn)行設(shè)置,SQLSERVER會簡單的截?cái)嘧詈笠粋€(gè)字節(jié)。因此,如果你讀取一個(gè)chunk并且位數(shù)已經(jīng)超出了磁盤的范圍,你可以假設(shè)剩余的位并沒有設(shè)置

解析一個(gè)vardecimal值最后,我們準(zhǔn)備去解析一個(gè)vardecimal 值(使用C#實(shí)現(xiàn))!我們將使用先前的例子,存儲123.45值使用decimal(5,2)列。在磁盤上,我們讀取下面的字節(jié)數(shù)組根據(jù)調(diào)用的順序

Hex: C2       1E       DC       20Bin: 11000010 00011110 11011100 00100000

讀取符號位讀取符號位相對來說比較簡單。我們將只需要在第一個(gè)字節(jié)上讀取:

通過位運(yùn)算符我們將右移7位,剩下的位是最重要的位。這意味著我們將得到值1 表示正數(shù)的符號位,如果是0表示負(fù)數(shù)

decimal sign = (value[0] >> 7) == 1 ? 1 : -1;

讀取指數(shù)下面(技術(shù)上來講這7個(gè)位是緊跟著符號位的)的7位包含了指數(shù)值

將十六進(jìn)制值0b1000010 轉(zhuǎn)換為進(jìn)制值得到的結(jié)果是66.我們知道指數(shù)總是有偏移值64,我們需要將存儲的值減去64從而得到實(shí)際值:

Exponent = 0b1000010 – 0n64 <=> Exponent = 66 – 64 = 2

讀取尾數(shù)接下來就是尾數(shù)值。前面提到,我們需要讀取一個(gè)10個(gè)位的chunk,并且需要注意那些被截?cái)嗟牟糠?/p>

首先,我們需要知道有多少有用的位。這樣做很簡單,我們只需簡單的將尾數(shù)的字節(jié)數(shù)(除了第一個(gè)字節(jié)之外的所有字節(jié))乘以8

int totalBits = (value.Length - 1) * 8;

一旦我們知道有多少位是可用的(在這個(gè)例子里 2個(gè)chunk 24位=3個(gè)字節(jié)* 8位),我們可以計(jì)算chunks的數(shù)目

int mantissaChunks = (int)Math.Ceiling(totalBits / 10d);

因?yàn)槊總€(gè)塊占用10位,我們只需要的比特總數(shù)除以10。如果有填充最后,匹配一個(gè)字節(jié)邊界,它將是0的,不會改變最終的結(jié)果。因此為2字節(jié)尾數(shù)我們將有8位備用,將非標(biāo)準(zhǔn)都是0。

對于一個(gè)3字節(jié)尾數(shù)我們將有4位,再次添加0尾數(shù)總額。

因?yàn)槊總€(gè)chunk占用10個(gè)位,我們只需要除以將位的總數(shù)除以10。如果在結(jié)尾有占位,為了匹配位的邊界,SQLSERVER會填充0但是這個(gè)不會影響上面公式得出的結(jié)果。

因此,對于一個(gè)2個(gè)字節(jié)的尾數(shù)我們會有8bit(這里作者是不是錯(cuò)了?應(yīng)該是6bit吧) 是剩余的,這些剩余位都會被無意義的0填充。對于一個(gè)3字節(jié) 的尾數(shù)我們會有4bit 剩余,

再一次在總的尾數(shù)值上填充0

這里我們準(zhǔn)備讀取chunk的值。在讀取之前,我們需要分配兩個(gè)變量

decimal mantissa = 0;int bitPointer = 8;

可變長的尾數(shù)值是由尾數(shù)值進(jìn)行累加的,每次我們讀取一個(gè)新10-bit chunk值就累加一次。bitPointer 是一個(gè)指針指向當(dāng)前讀取到的位。我們不準(zhǔn)備讀取第一個(gè)字節(jié),我們會從第8位開始讀取(從0開始數(shù),因此第8位 =第二個(gè)字節(jié)的第一位)

看一下這些位 他們可以簡單的看成是一條long stream --我們只需要從左到右進(jìn)行讀取,對吧?不完全是,你可否記得,最右邊的位是最重要的,因此最右邊的位應(yīng)該是我們最先要讀取的。然而,我們需要一次讀取一個(gè)字節(jié)。同樣,整體的方向是按chunk為單位的話是從左到右讀取。

一旦我們達(dá)到chunk的位置,我們每次就需要一個(gè)字節(jié)一個(gè)字節(jié)地讀取。bits1-8 在第一個(gè)字節(jié)里讀取,bits9-10 在第二個(gè)字節(jié)里讀取,

下面圖片中橙色的箭頭(最大的那個(gè)箭頭指示字節(jié)讀取順序(chunkwise)從左向右,而每個(gè)獨(dú)立的字節(jié)內(nèi)部的讀取順序是小箭頭那個(gè) 從右向左)

為了方便訪問所有的bits,和避免做太多的人工的位移操作,我實(shí)例化一個(gè)BitArray 類 ,這個(gè)類包含了所有的數(shù)據(jù)位:

var mantissaBits = new BitArray(value);

使用這個(gè)類,你必須知道bit 數(shù)組如何跟字節(jié)進(jìn)行映射。形象的描述,他會像下面那樣,mantissaBits 數(shù)組指針在圖片的上面:

我知道這看起來很復(fù)雜,不過所有這些復(fù)雜的事情只不過是需要知道指針的指向。我們的代碼里面是字節(jié)數(shù)組。我們訪問每一個(gè)獨(dú)立的bits的方式是通過mantissaBits 數(shù)組,這個(gè)數(shù)組只是一個(gè)很大的指向獨(dú)立的bits的數(shù)組指針。

看一下第一個(gè)8 bits,manitssaBits 數(shù)組按照我們的讀取方向很好地排列。第一個(gè)條目(mantissaBits[0])指向第一個(gè)字節(jié)的最右一位。第二個(gè)條目指向第二個(gè)字節(jié),以此類推。因此,第一個(gè)8 bits是直接讀取的。然而,后面的兩個(gè),在manitssaBits 數(shù)組里面他們需要我們跳過6個(gè)條目以至于我們讀取條目14

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 锡林浩特市| 敦煌市| 古浪县| 洛隆县| 诸暨市| 大石桥市| 准格尔旗| 安塞县| 新丰县| 邮箱| 乌什县| 卢湾区| 邓州市| 玉树县| 湘西| 贺州市| 澄江县| 资中县| 兴义市| 桃源县| 新野县| 保亭| 梧州市| 蒲城县| 武平县| 昌都县| 商城县| 塔城市| 麻城市| 和林格尔县| 漠河县| 皋兰县| 白城市| 洛川县| 武功县| 洛宁县| 大石桥市| 出国| 德格县| 涡阳县| 新宾|