MongoDB中关于64位整型存储解决方案
為什么80%的碼農都做不了架構師?>>> ??
社區內一哥們@smcboy 提出關于php中操作MongoDB存儲整數問題,找到點資料花點時間翻譯過來,是個很好的學習方式。@紅薯 那篇討論我的修改回復,仍然沒有更新可惡啊~!!說實話我就是高一英語水平?為了這篇文章我算是絞盡腦汁,翻譯了大半天,累死我了。科學精神可貴、可貴!!
在我當前項目中大量是MongoDB,正在從傳統RDBMS過度到key-value存儲。Facebook中用戶標識UserID使用64位Int數據類型存儲,杯具的是 MongoDB的PHP驅動只支持32位整型數據,導致UserID被截斷無法處理Facebook用戶信息。
MongoDB數據采用BSON(Binary JSON)文檔型存儲,BSON有兩種整型數據類型,1、32位有符號整型數據(INT); 2、64位有符號型整型數據(LONG)。由于PHP不支持大于8個字節整數,所以MongoDB PHP驅動只支持32位有符號整型數據存儲。然而這樣不是絕對的,在C類型 long 為64位平臺上,PHP仍然可以正常支持64位整型數據; 除了在Windowns上,其他平臺上C中long類型總是32位。
當PHP中整型存儲到MongoDB中,PHP驅動會采用最低兼容原則用32位進行轉換存儲到MongoDB文檔中。下面是測試案例(測試平臺為 64位):
輸出:
int(1015724736) 二進制解析:1234567890123456 = 1000110001011010101001111001000101010111010110000001015724736 = 111100100010101011101011000000
上面可以看出數據已被截斷,這顯然不是我想要的。為了解決這個問題,從PHP中存儲到MongoDB,我們可以采用原生的PHP整型數據。注意!不是去修改MongoDB相關驅動程序,而在PHP中配置一個簡單參數 mongo.native_long ,從而避免大量應用程序改動。當 mongo.native_long 參數開啟之后,我們可以看到如下不同的結果:
代碼:
ini_set('mongo.native_long', 1); $c->insert(array('number' => 1234567890123456));$r = $c->findOne(); var_dump($r['number']); 輸出:
int(1234567890123456)
在64位平臺中,PHP程序中配置mongo.native_long 允許使用完整64位整型存儲到MongoDB,本例中這種方式存儲到MongoDB中類型為BSON LONG, 如果未開啟此配置則類型為BSON INT類型。該配置對從MongoDB讀取數據到PHP中同樣有效。如果關閉該配置,當從MongoDB取出數據時PHP驅動會把 BSON LONG 類型轉換為PHP的double類型,造成精度損失。下面看個例子:
ini_set('mongo.native_long', 1); //開啟配置 $c->insert(array('number' => 12345678901234567));ini_set('mongo.native_long', 0); //關閉配置 $r = $c->findOne(); var_dump($r['number']); 輸出:
float(1.2345678901235E+16) 在32位平臺中 mongo.native_log 參數配置不起任何作用,仍然會以BSON INT 類型存儲。
然而當該配置開啟時從Mongo中取出 BSON LONG類型數據,MongoCursorException 會提示關于精度損失問題。
MongoCursorException: Can not natively represent the long 1234567890123456 on this platform
當該配置關閉時 BSON LONG 數據,為了兼容PHP會把 BSON INT 轉成float類型
盡管在64位平臺上可以使用該配置mongo.native_long達到支持64位整型的目的,但是并沒有提供32平臺上的解決方案,去防止BSON LONG 數據的精度丟失問題,僅僅不負責任的拋出一個精度丟失的異常信息( 詳情)。
工作中使用64位整位還是比較靠譜的,俺自己添加了兩個類庫 MongoInt32 和 MongoInt64,這兩個類簡單的封裝了用字符串表示數字。使用方式:?
$int32 = new MongoInt32("32091231"); $int64 = new MongoInt64("1234567980123456"); 使用該對象可以像正常使用插入、更新、查詢等操作
例如:
$m = new Mongo(); $c = $m->selectCollection('test', 'inttest'); $c->remove(array());$c->insert(array('int32' => new MongoInt32("1234567890"),'int64' => new MongoInt64("12345678901234567"), ));$r = $c->findOne(); var_dump($r['int32']); var_dump($r['int64']); 輸出結果:?
int(1234567890) float(1.2345678901235E+16) 可以看到對返回結果沒任何改變。BSON INT類型仍然是 int型,BSON LONG 類型變為 double類型。如果我啟用 mongo.native_long 配置,通過MongoInt64類庫轉換,在64位平臺上,PHP中獲取 BSON LONG 會返回正確int型,在32位平臺上MongoCursorException會拋出提示信息。
為了在32位平臺中,從MongoDB內取出 64位整型數據,需要配置另一個參數 mongo.long_as_object ,開啟后,BSON LONG取出后以一個MongoInt64對象返回。
案例:
$m = new Mongo(); $c = $m->selectCollection('test', 'inttest'); $c->remove(array());$c->insert(array('int64' => new MongoInt64("12345678901234567"), ));ini_set('mongo.long_as_object', 1); $r = $c->findOne(); var_dump($r['int64']); echo $r['int64'], "\n"; echo $r['int64']->value, "\n"; 輸出:
object(MongoInt64)#7 (1) {["value"]=>string(17) "12345678901234567" } 12345678901234567 12345678901234567 MongoInt32和MongoInt64 類基于對象的__toString()實現,所以返回的value值可以直接進行 echo,你只能獲取一個整型字符串,所以請意識到MongoDB是類型敏感的,不會用對待字符串的方式對待數字,數字就是數字。
案例(64位平臺):
ini_set('mongo.native_long', 1);$m = new Mongo(); $c = $m->selectCollection('test', 'inttest'); $c->remove(array());$nr = "12345678901234567"; $c->insert(array('int64' => new MongoInt64($nr)));$r = $c->findOne(array('int64' => $nr)); // $nr is a string here var_dump($r['int64']); $r = $c->findOne(array('int64' => (int) $nr)); var_dump($r['int64']); 輸出:
NULL int(12345678901234567)
下面列出關于不同的參數啟用狀態,整型轉換情況:
PHP to ?MongoDB (32位系統)| From PHP | Stored in Mongo | |
| native_long=0 | native_long=1 | |
| 1234567 | INT(1234567) | INT(1234567) |
| 123456789012 | FLOAT(123456789012) | FLOAT(123456789012) |
| MongoInt32("1234567") | INT(1234567) | INT(1234567) |
| MongoInt64("123456789012") | LONG(123456789012) | LONG(123456789012) |
PHP to ?MongoDB (64位系統):
| From PHP | Stored in Mongo | |
| native_long=0 | native_long=1 | |
| 1234567 | INT(1234567) | LONG(1234567) |
| 123456789012 | garbage | LONG(123456789012) |
| MongoInt32("1234567") | INT(1234567) | INT(1234567) |
| MongoInt64("123456789012") | LONG(123456789012) | LONG(123456789012) |
| Stored in Mongo | Returned to PHP as | ||
| long_as_object=0 | long_as_object=1 | ||
| native_long=0 | native_long=1 | ||
| INT(1234567) | int(1234567) | int(1234567) | int(1234567) |
| LONG(123456789012) | float(123456789012) | MongoCursorException | MongoInt64("123456789012") |
Mongo to PHP (64位系統):
| Stored in Mongo | Returned to PHP as | ||
| long_as_object=0 | long_as_object=1 | ||
| native_long=0 | native_long=1 | ||
| INT(1234567) | int(1234567) | int(1234567) | int(1234567) |
| LONG(123456789012) | float(123456789012) | int(123456789012) | MongoInt64("123456789012") |
總結:
綜上所述可以看到想獲得64位的支持還是很棘手的,如果你只需要在64為平臺上運行代碼,我們推薦使用 mongo.native_long=1 配置參數。當整數存儲到MongoDB,取出是仍然是整型數據,從而達到支持64位的目的。
如果你丫就是想要在32位平臺(包含Windows 64位上的PHP),你沒辦法使用得到可靠的整型數據,必須使用MongoInt64 類來實現。這也會帶來其他問題,如:你必須在初始化的時候處理字符串類型的數字。也要注意MongoDB Shell 將所有的數字作為float浮點型數據處理,這并不能代表64位整型數字,相反將作為浮點型數字。所有不要在shell模式下進行數據修改,這樣會導致類型轉換!!
案例:
$m = new Mongo(); $c = $m->selectCollection('test', 'inttest'); $c->remove(array());$c->insert(array('int64' => new MongoInt64("123456789012345678"))); MongoDB Shell模式下: $ mongo MongoDB shell version: 1.4.4 url: test connecting to: test type "help" for help > use test switched to db test > db.inttest.find() { "_id" : ObjectId("4c5ea6d59a14ce1319000000"), "int64" : { "floatApprox" : 123456789012345680, "top" : 28744523, "bottom" : 2788225870 } } 當我們通過驅動獲取支持64位數據,可以得到靠譜的結果: ini_set('mongo.long_as_object', 1); $r = $c->findOne(); var_dump($r['int64']); 輸出:object(MongoInt64)#7 (1) {["value"]=>string(18) "123456789012345678" }
這個新函數方式將會在 ?mongo 1.0.9 release 版本中推出,可以通過PRCL ?pecl install mongo?獲取。
剩下的就靠命運了,祝你好運。
翻譯:OSC民工
原文鏈接:http://derickrethans.nl/64bit-ints-in-mongodb.html
轉載于:https://my.oschina.net/kisswu/blog/122338
總結
以上是生活随笔為你收集整理的MongoDB中关于64位整型存储解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 昨天安装复习中遇到的问题小结
- 下一篇: Udp通讯(零基础)