MongoDB中關於64位整型存儲解決方案

社區內一哥們@smcboy 提出關於php中操做MongoDB存儲整數問題,找到點資料花點時間翻譯過來,是個很好的學習方式。@紅薯 那篇討論個人修改回覆,仍然沒有更新可惡啊~!!說實話我就是高一英語水平 爲了這篇文章我算是絞盡腦汁,翻譯了大半天,累死我了。科學精神難得、難得!! php

在我當前項目中大量是MongoDB,正在從傳統RDBMS過分到key-value存儲。Facebook中用戶標識UserID使用64位Int數據類型存儲,杯具的是 MongoDB的PHP驅動只支持32位整型數據,致使UserID被截斷沒法處理Facebook用戶信息。 html

MongoDB數據採用BSON(Binary JSON)文檔型存儲,BSON有兩種整型數據類型,一、32位有符號整型數據(INT); 二、64位有符號型整型數據(LONG)。因爲PHP不支持大於8個字節整數,因此MongoDB PHP驅動只支持32位有符號整型數據存儲。然而這樣不是絕對的,在C類型 long 爲64位平臺上,PHP仍然能夠正常支持64位整型數據; 除了在Windowns上,其餘平臺上C中long類型老是32位。


當PHP中整型存儲到MongoDB中,PHP驅動會採用最低兼容原則用32位進行轉換存儲到MongoDB文檔中。下面是測試案例(測試平臺爲 64位): git

$m = new Mongo();
$c = $m->selectCollection('test', 'inttest');
$c->remove(array());
//插入大於32位數據
$c->insert(array('number' => 1234567890123456));

$r = $c->findOne();
echo $r['number'], "\n";

輸出: github

int(1015724736)
二進制解析:
1234567890123456 = 100011000101101010100111100100010101011101011000000
      1015724736 =                      111100100010101011101011000000

上面能夠看出數據已被截斷,這顯然不是我想要的。爲了解決這個問題,從PHP中存儲到MongoDB,咱們能夠採用原生的PHP整型數據。注意!不是去修改MongoDB相關驅動程序,而在PHP中配置一個簡單參數 mongo.native_long ,從而避免大量應用程序改動。當 mongo.native_long 參數開啓以後,咱們能夠看到以下不一樣的結果:

代碼:
mongodb

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類型,形成精度損失。下面看個例子: shell

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)

下面列出關於不一樣的參數啓用狀態,整型轉換狀況: json

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)

Mongo to PHP (32位系統)

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

相關文章
相關標籤/搜索