Python、sqlalchemy:如何提高加密sqlite数据库的性能?
Python, sqlalchemy: how to improve performance of encrypted sqlite database?
我有一个简单的服务应用程序:python、tornado 网络服务器、sqlite 数据库。数据库已加密。
问题是处理非常简单的 http 请求需要大约 300 毫秒。
从日志中我可以看出,大部分时间都花在了第一个 sql 请求的处理上,无论这个第一个请求多么简单。后续 sql 请求的处理速度要快得多。但是随后服务器开始处理下一个 http 请求,第一个 sql 请求又很慢。
如果我关闭数据库加密,问题就消失了:sql 请求的处理时间不取决于请求是否是第一个请求,我的服务器响应时间减少了 10 到 15。
不太明白是怎么回事。看起来 sqlalchemy 每次启动新会话时都会读取和解密数据库文件。有什么办法可以解决这个问题?
由于 pysqlite 或 sqlite3
模块如何工作 SQLAlchemy defaults to using a NullPool
with file-based databases。这解释了为什么每个请求都会解密您的数据库:NullPool
在连接关闭时丢弃连接。这样做的原因是 pysqlite 的默认行为是不允许在多个线程中使用连接,并且在没有加密的情况下创建新连接非常快。
Pysqlite 确实有一个未记录的标志 check_same_thread
可用于禁用检查,但应谨慎处理线程之间的共享连接并且 SQLAlchemy 文档顺便提到 NullPool
与 SQLite 的文件锁定配合使用效果很好。
根据您的 Web 服务器,您可以使用 SingletonThreadPool
,这意味着线程中的所有连接都是相同的连接:
engine = create_engine('sqlite:///my.db',
poolclass=SingletonThreadPool)
如果您喜欢冒险并且您的 Web 服务器在使用时不在线程之间共享连接/会话(例如使用范围会话),那么您可以尝试使用与 check_same_thread=False
配对的不同池化策略:
engine = create_engine('sqlite:///my.db',
poolclass=QueuePool,
connect_args={'check_same_thread':False})
为了加密数据库,sqlcipher 根据我提供的密码创建了一个密钥。此操作在设计上会消耗资源。
但可以不使用密码,而是使用 256 位原始密钥。在这种情况下,sqlcipher 不必生成加密密钥。
最初我的代码是:
session.execute('PRAGMA KEY = "MY_PASSPHRASE";')
为了使用原始密钥,我将这一行更改为:
session.execute('''PRAGMA KEY = "x'<the key>'";''')
其中 <the key>
是 64 个字符长的十六进制字符串。
结果是小请求的速度提高了 20 倍以上。
仅供参考:要将数据库转换为使用新的加密密钥,应执行以下命令:
PRAGMA KEY = ""MY_PASSPHRASE";
PRAGMA REKEY = "x'<the key>'";
相关问题:
关于 sqlcipher 命令的一些信息以及密钥和原始密钥之间的区别:https://www.zetetic.net/sqlcipher/sqlcipher-api/
我有一个简单的服务应用程序:python、tornado 网络服务器、sqlite 数据库。数据库已加密。
问题是处理非常简单的 http 请求需要大约 300 毫秒。
从日志中我可以看出,大部分时间都花在了第一个 sql 请求的处理上,无论这个第一个请求多么简单。后续 sql 请求的处理速度要快得多。但是随后服务器开始处理下一个 http 请求,第一个 sql 请求又很慢。
如果我关闭数据库加密,问题就消失了:sql 请求的处理时间不取决于请求是否是第一个请求,我的服务器响应时间减少了 10 到 15。
不太明白是怎么回事。看起来 sqlalchemy 每次启动新会话时都会读取和解密数据库文件。有什么办法可以解决这个问题?
由于 pysqlite 或 sqlite3
模块如何工作 SQLAlchemy defaults to using a NullPool
with file-based databases。这解释了为什么每个请求都会解密您的数据库:NullPool
在连接关闭时丢弃连接。这样做的原因是 pysqlite 的默认行为是不允许在多个线程中使用连接,并且在没有加密的情况下创建新连接非常快。
Pysqlite 确实有一个未记录的标志 check_same_thread
可用于禁用检查,但应谨慎处理线程之间的共享连接并且 SQLAlchemy 文档顺便提到 NullPool
与 SQLite 的文件锁定配合使用效果很好。
根据您的 Web 服务器,您可以使用 SingletonThreadPool
,这意味着线程中的所有连接都是相同的连接:
engine = create_engine('sqlite:///my.db',
poolclass=SingletonThreadPool)
如果您喜欢冒险并且您的 Web 服务器在使用时不在线程之间共享连接/会话(例如使用范围会话),那么您可以尝试使用与 check_same_thread=False
配对的不同池化策略:
engine = create_engine('sqlite:///my.db',
poolclass=QueuePool,
connect_args={'check_same_thread':False})
为了加密数据库,sqlcipher 根据我提供的密码创建了一个密钥。此操作在设计上会消耗资源。
但可以不使用密码,而是使用 256 位原始密钥。在这种情况下,sqlcipher 不必生成加密密钥。
最初我的代码是:
session.execute('PRAGMA KEY = "MY_PASSPHRASE";')
为了使用原始密钥,我将这一行更改为:
session.execute('''PRAGMA KEY = "x'<the key>'";''')
其中 <the key>
是 64 个字符长的十六进制字符串。
结果是小请求的速度提高了 20 倍以上。
仅供参考:要将数据库转换为使用新的加密密钥,应执行以下命令:
PRAGMA KEY = ""MY_PASSPHRASE";
PRAGMA REKEY = "x'<the key>'";
相关问题:
关于 sqlcipher 命令的一些信息以及密钥和原始密钥之间的区别:https://www.zetetic.net/sqlcipher/sqlcipher-api/