为什么应该将序列化的数据存储到数据库而不是原始代码?

2 投票
9 回答
4475 浏览
提问于 2025-04-15 15:43

如果我们有一些代码(比如一种数据结构),需要存储到数据库里,很多人会建议我们存储序列化后的数据,而不是原始的代码字符串。

我不太明白为什么我们应该更喜欢序列化的数据。

举个简单的例子(用Python):

我们有一个字段要存储一个Python的字典,比如:

{ "name" : "BMW", "category":"car", "cost" : "200000"}

我们可以用pickle(一个Python模块)来序列化这个字典,然后把序列化后的数据存到数据库字段里。

或者我们也可以直接把字典的字符串存到数据库,不进行序列化。

因为我们需要把字符串转换回Python数据,这两种方法都很简单,分别可以用pickle.loads和exec来实现。

那么我们应该选择哪种方式呢?为什么呢?是因为exec比pickle慢得多,还是有其他原因呢?

谢谢。

9 个回答

3

我更喜欢用像JSON这样的标准格式来存储这类数据在数据库里。这样做的好处是,使用这些数据的人可以用其他编程语言来写,而不仅仅是Python。而且,JSON格式基本上是人类可读的,读起来比较简单。此外,用SQL查询这些数据也比用“腌制”对象要方便得多。

3

如果让我在把数据转成像JSON这样的格式和存储一个“腌制”的数据结构之间选择,我每次都会选择JSON。除了大家提到的安全问题,最大的原因是可移植性。如果将来需要把你的系统迁移到其他语言,存储一个“腌制”的Python对象会让这变得很困难。此外,其他应用程序可能也需要访问你存储的数据,但我不能具体说,因为我不了解你的情况。

另外,如果你的系统需要进行任何类型的过滤,存储数据为JSON字符串也不是最好的选择。如果可以的话,而且字段数量是固定的,我会很想把它们拆分成更小的元素。这样搜索和过滤会变得更简单、更高效。

10

或者我们可以直接把字典字符串存到数据库里,而不需要序列化。

其实并没有所谓的“字典字符串”。把字典转换成字符串有很多种方法;你可能在想 repr,也许是 eval 用来把字典拿回来(你提到 exec,但那实在是太荒谬了:你想执行什么语句呢...?!我觉得你可能是想说 eval)。这些都是不同的序列化方法,各有优缺点,在很多情况下,优缺点更倾向于使用 pickle(cPickle,因为速度快,协议 -1 意味着“你能做到的最好”)。

性能肯定是个问题,比如说你存储的东西大小...:

$ python -c 'import cPickle; d=dict.fromkeys(range(99), "banana"); print len(repr(d))'
1376
$ python -c 'import cPickle; d=dict.fromkeys(range(99), "banana"); print len(cPickle.dumps(d,-1))'
412

...你为什么每次序列化这样的字典时要存 1.4 KB,而不是 0.4 KB 呢...?-)

编辑:因为有人提到 Json,值得一提的是,这里 json 占用了 1574 字节——甚至比笨重的 repr 还要大!

至于速度...

$ python -mtimeit -s'import cPickle; d=dict.fromkeys(range(99), "chocolate")' 'eval(repr(d))'
1000 loops, best of 3: 706 usec per loop
$ python -mtimeit -s'import cPickle; d=dict.fromkeys(range(99), "chocolate")' 'cPickle.loads(cPickle.dumps(d, -1))'
10000 loops, best of 3: 70.2 usec per loop

...为什么要花 10 倍的时间?有什么好处值得你付出这么高的代价?

编辑:json 需要 2.7 毫秒——几乎是 cPickle 的 四十 倍慢。

还有通用性——并不是每个可序列化的对象都能通过 repr 和 eval 正确地来回转换,而 pickle 的通用性要强得多。例如:

$ python -c'def f(): pass
d={23:f}
print d == eval(repr(d))'
Traceback (most recent call last):
  File "<string>", line 3, in <module>
  File "<string>", line 1
    {23: <function f at 0x241970>}
         ^
SyntaxError: invalid syntax

$ python -c'import cPickle
def f(): pass
d={"x":f}
print d == cPickle.loads(cPickle.dumps(d, -1))'
True

编辑:在来回转换方面,json 的通用性甚至比 repr 还要差。

所以,比较这两种序列化方法(pickle 和 repr/eval),我们可以看到:pickle 更加通用,速度可以快 10 倍,存储在数据库里的空间也可以少 3 倍。

你认为 repr/eval 有什么补偿性的优势呢...?

顺便说一下,我看到一些回答提到安全性,但这并不是一个真正的问题:pickle 也不安全(用 eval 处理不可信的字符串可能更明显,但反序列化一个不可信的字符串同样不安全,只是方式更微妙和复杂)。

编辑:json 更安全。是否值得为此付出在大小、速度和通用性上的巨大代价,这是一个值得深思的权衡。在大多数情况下,这并不值得。

撰写回答