防止 SQLite SQL 注入并优化性能(九)

在开发 Web 应用时,确保数据库的安全性和性能至关重要。本文将详细介绍如何防止 SQLite 中的 SQL 注入攻击,以及如何使用 VACUUM 命令和自动 VACUUM 功能来优化数据库性能。此外,我们还将探讨 SQLite 中的日期和时间函数及其用法,帮助你在实际项目中更好地处理时间数据。

防止 SQL 注入

SQL 注入是一种常见的安全漏洞,攻击者通过在输入字段中插入恶意 SQL 代码,试图操纵数据库查询。如果你的应用程序允许用户输入数据并将其插入到 SQLite 数据库中,那么你就需要特别小心,确保这些输入经过严格的验证和转义。

用户输入验证

永远不要信任用户的输入。在处理用户提交的数据时,务必进行验证。例如,你可以使用正则表达式来限制用户名的格式,确保它只包含字母数字字符和下划线,长度在 8 到 20 个字符之间:

if (preg_match("/^\w{8,20}$/", $_GET['username'])) {
    $db = new SQLite3('database.db');
    $result = $db->query("SELECT * FROM users WHERE username='{$_GET['username']}'");
} else {
    echo "用户名不符合要求";
}

转义特殊字符

为了进一步增强安全性,可以使用 SQLite 提供的转义函数来处理用户输入。在 PHP 中,可以使用 SQLite3::escapeString() 方法来转义特殊字符:

$username = SQLite3::escapeString($_GET['username']);
$db = new SQLite3('database.db');
$result = $db->query("SELECT * FROM users WHERE username='{$username}'");

参数化查询

参数化查询是防止 SQL 注入的最佳实践之一。通过使用参数化查询,可以确保用户输入的数据不会被解析为 SQL 代码:

$db = new SQLite3('database.db');
$stmt = $db->prepare("SELECT * FROM users WHERE username=:username");
$stmt->bindValue(':username', $_GET['username'], SQLITE3_TEXT);
$result = $stmt->execute();

使用 EXPLAIN 分析查询

EXPLAIN 和 EXPLAIN QUERY PLAN 是 SQLite 提供的强大工具,用于分析查询的执行计划。这些工具可以帮助你了解查询是如何执行的,从而优化查询性能。

EXPLAIN 语法

EXPLAIN 关键字用于显示查询的低级操作步骤:

EXPLAIN [SQLite Query]

EXPLAIN QUERY PLAN 语法

EXPLAIN QUERY PLAN 关键字用于显示查询的高级执行计划:

EXPLAIN QUERY PLAN [SQLite Query]

示例

假设我们有一个名为 COMPANY 的表,包含以下记录:

ID          NAME        AGE         ADDRESS     SALARY
----------  ----------  ----------  ----------  ----------
1           Paul        32          California  20000.0
2           Allen       25          Texas       15000.0
3           Teddy       23          Norway      20000.0
4           Mark        25          Rich-Mond   65000.0
5           David       27          Texas       85000.0
6           Kim         22          South-Hall  45000.0
7           James       24          Houston     10000.0

我们可以使用 EXPLAIN 来查看查询的详细步骤:

EXPLAIN SELECT * FROM COMPANY WHERE Salary >= 20000;

输出结果如下:

addr        opcode      p1          p2          p3
----------  ----------  ----------  ----------  ----------
0           Goto        0           19
1           Integer     0           0
2           OpenRead    0           8
3           SetNumColu  0           5
4           Rewind      0           17
5           Column      0           4
6           RealAffini  0           0
7           Integer     20000       0
8           Lt          357         16          collseq(BI
9           Rowid       0           0
10          Column      0           1
11          Column      0           2
12          Column      0           3
13          Column      0           4
14          RealAffini  0           0
15          Callback    5           0
16          Next        0           5
17          Close       0           0
18          Halt        0           0

19          Transactio  0           0
20          VerifyCook  0           38
21          Goto        0           1
22          Noop        0           0

使用 EXPLAIN QUERY PLAN 查看查询的高级执行计划:

EXPLAIN QUERY PLAN SELECT * FROM COMPANY WHERE Salary >= 20000;

输出结果如下:

order       from        detail
----------  ----------  -------------
0           0           TABLE COMPANY

优化数据库性能

手动 VACUUM

VACUUM 命令用于优化数据库文件,通过重新组织数据,消除空闲页,使数据库文件更加紧凑。这不仅提高了查询性能,还减少了磁盘空间占用。

语法

在命令行中运行 VACUUM 命令:

sqlite3 database_name "VACUUM;"

在 SQLite 提示符中运行 VACUUM 命令:

VACUUM;

你也可以针对特定的表运行 VACUUM:

VACUUM table_name;

自动 VACUUM

自动 VACUUM 功能可以在数据库操作过程中自动优化数据库文件。通过设置 auto_vacuum 参数,可以选择不同的自动 VACUUM 模式:

  • NONE:禁用自动 VACUUM
  • INCREMENTAL:增量自动 VACUUM
  • FULL:完全自动 VACUUM

设置自动 VACUUM

在 SQLite 提示符中设置 auto_vacuum 参数:

PRAGMA auto_vacuum = NONE;  -- 禁用自动 VACUUM
PRAGMA auto_vacuum = INCREMENTAL;  -- 启用增量自动 VACUUM
PRAGMA auto_vacuum = FULL;  -- 启用完全自动 VACUUM

检查当前的 auto_vacuum 设置:

sqlite3 database_name "PRAGMA auto_vacuum;"

处理日期和时间

SQLite 提供了多种日期和时间函数,帮助你在应用程序中处理时间数据。这些函数包括 date、time、datetime、julianday 和 strftime。

日期和时间函数

  • date(timestring, modifier, modifier, ...): 返回日期,格式为 YYYY-MM-DD
  • time(timestring, modifier, modifier, ...): 返回时间,格式为 HH:MM:SS
  • datetime(timestring, modifier, modifier, ...): 返回日期和时间,格式为 YYYY-MM-DD HH:MM:SS
  • julianday(timestring, modifier, modifier, ...): 返回从格林尼治时间的公元前 4714 年 11 月 24 日正午算起的天数
  • strftime(format, timestring, modifier, modifier, ...): 根据指定的格式字符串返回格式化的日期和时间

时间字符串

时间字符串可以采用以下格式:

  • YYYY-MM-DD: 2010-12-30
  • YYYY-MM-DD HH:MM: 2010-12-30 12:10
  • YYYY-MM-DD HH:MM:SS.SSS: 2010-12-30 12:10:04.100
  • MM-DD-YYYY HH:MM: 12-30-2010 12:10
  • HH:MM: 12:10
  • YYYY-MM-DDTHH:MM: 2010-12-30 12:10
  • HH:MM:SS: 12:10:01
  • YYYYMMDD HHMMSS: 20101230 121001
  • now: 当前日期和时间

修饰符

时间字符串后可以跟随零个或多个修饰符,用于调整日期和时间:

  • NNN days
  • NNN hours
  • NNN minutes
  • NNN.NNNN seconds
  • NNN months
  • NNN years
  • start of month
  • start of year
  • start of day
  • weekday N
  • unixepoch
  • localtime
  • utc

示例

计算当前日期:

SELECT date('now');

计算当前月份的最后一天:

SELECT date('now', 'start of month', '+1 month', '-1 day');

计算给定 UNIX 时间戳 1092941466 的日期和时间:

SELECT datetime(1092941466, 'unixepoch');

计算给定 UNIX 时间戳 1092941466 相对本地时区的日期和时间:

SELECT datetime(1092941466, 'unixepoch', 'localtime');

计算当前的 UNIX 时间戳:

SELECT strftime('%s', 'now');

计算美国“独立宣言”签署以来的天数:

SELECT julianday('now') - julianday('1776-07-04');

计算从 2004 年某一特定时刻以来的秒数:

SELECT strftime('%s', 'now') - strftime('%s', '2004-01-01 02:34:56');

计算当年 10 月的第一个星期二的日期:

SELECT date('now', 'start of year', '+9 months', 'weekday 2');

计算从 UNIX 纪元算起的以秒为单位的时间(包括小数部分):

SELECT (julianday('now') - 2440587.5) * 86400.0;

在 UTC 与本地时间值之间进行转换:


SELECT time('12:00', 'localtime');
SELECT time('12:00', 'utc');

总结

通过本文,我们详细了解了如何防止 SQLite 中的 SQL 注入攻击,优化数据库性能的方法,以及如何使用 SQLite 的日期和时间函数。希望这些知识能帮助你在实际项目中更好地管理和优化 SQLite 数据库。