什么更有效率 - 将日志存储在SQL数据库中还是文件中?

49

我有几个脚本经常由cron加载。现在我不存储任何日志,所以如果任何脚本无法加载,我在看到结果之前就不会知道它失败了——甚至当我注意到结果不正确时,我也无能为力,因为我不知道哪个脚本失败了。

我决定存储日志,但我仍然不确定如何做。所以,我的问题是——将日志存储在SQL数据库中还是文件中更有效?

我可以在mysql数据库中创建“日志”表,并将每个日志存储在单独的行中,或者我可以使用php的file_put_contents或fopen/fwrite将日志存储在单独的文件中。

我的脚本在工作时大约会添加5个日志(总共)每分钟。我进行了几次测试来确定什么更快——fopen/fwrite还是mysql的插入。我循环执行了3000次“insert”语句以生成3000行,并循环执行了3000次fopen/fwrite以生成带有示例文本的3000个文件。fwrite的速度比sql的insert快4-5倍。我进行了第二个循环——我循环执行了“select”语句,并将其分配给一个字符串3000次——我还使用“fopen”打开了3000个文件,并将结果分配给该字符串。结果是相同的——fopen/fwrite完成任务的速度快4-5倍。

因此,对于所有有经验的程序员——您在存储日志方面的经验是什么?有什么建议吗?

// 04.09.2011 编辑 - 感谢大家的答案,它们对我很有帮助。每篇文章都很有价值,所以只选择一个答案是非常困难的;-)


@慢的原因是在insert语句的开销。如果您将数据添加到CSV文件中,并使用load data infile读取该文件,则4-5倍速度将迅速降至2倍,1倍用于编写CSV文件,1倍用于load data infile - Johan
1
@firian - 当出现问题时,您只需要触发脚本发送包含详细信息的电子邮件给您即可。 - ajreal
您可以使用缓存数据库,例如Redis或Memcache,并使用流程将其全部保存在MySQL中。您也可以直接使用MongoDB或使用Redis桥接器。Redis非常快速,而MongoDB较慢。MySQL非常非常慢xD。您还可以使用一些外部日志服务,例如loggly.com。 - user1710825
9个回答

20

使用文件记录日志更加高效,但是存储在数据库中的日志更容易阅读,即使在远程情况下(例如,如果需要,您可以编写Web前端)。

但请注意,连接和插入行到数据库中是容易出错的(例如,数据库服务器宕机、密码错误、资源不足等),那么如果您决定使用数据库,那么应该在哪里记录这些错误呢?


6
老板会通过电子邮件发送一条关键日志信息。 - Nick
1
将日志记录到文件作为备份,可以使用JSON格式以便在服务器恢复后易于解析,并发送电子邮件以通知关键错误。 - over_optimistic
2
你可以记录到数据库,但如果记录到数据库时出现错误,则应记录到文件中,同时记录原本应该记录到数据库的错误和尝试存储到日志数据库时遇到的错误。 - Daniel Valland

18
您可以使用类似于Zend_Log这样的组件,其原生支持附加到同一日志实例的写入器概念。通过这种方式,您可以将相同的消息记录到一个或多个不同的位置,而无需更改日志记录代码。而且您始终可以通过简单的方式更改代码以替换日志系统或添加新的日志系统。
对于您的问题,如果只有您(开发者)需要读取日志信息,则将日志记录到文件中更为简单和合适。
如果需要其他人通过 Web 界面读取日志或者需要具备搜索日志的能力,则应将日志记录到数据库中。正如其他人指出的那样,并发性也很重要,如果有很多用户,则将日志记录到数据库可能会更好地扩展。
最后,每分钟5条日志消息的日志频率几乎不需要应用程序使用 CPU,因此您不必担心性能问题。在您的情况下,我建议从日志文件开始,然后根据您的要求更改(或添加更多的写入器)。

3
使用的工具越复杂,越不稳定。保持简单,并使用直接的日志文件。 - Your Common Sense
@Col.Shrapnel,你使用的工具越复杂,它就越灵活。此外,Zend_Framework已经有三年多的历史,并且经过了充分的测试,应该非常稳定,你不觉得吗? - Fabio
1
灵活性并不是日志记录中最重要的特性之一,但容错性却是。如果您想要自己的记录器,并且带有二十一点和妓女,请进行*后处理,使用任何愿意将您的日志放入数据库、月球基地或其他地方的日志分析器。 - Your Common Sense
Zend_Log链接不再可访问。 - jawo

6

对您的发现进行评论。

关于写入文件,您可能是正确的。
关于读取,则完全错误。

向数据库写入:

  1. MyISAM在插入时会锁定整个表,导致锁定争用。使用行锁定的InnoDB。
  2. 与1相反。如果您想要在日志上执行全文搜索,请使用支持全文索引的MyISAM。
  3. 如果您希望速度真的很快,可以使用“内存”引擎,这将把表写入RAM中。当CPU负载低时,将数据传输到基于磁盘的表中。

从数据库中读取

这就是数据库真正发挥作用的地方。
您可以组合来自不同条目的各种信息,比从平面文件中执行这些操作要快得多且更容易。

SELECT logdate, username, action FROM log WHERE userid = '1' /*root*/ AND error = 10;

如果您在where子句中使用的字段上建立索引,则结果将几乎立即返回,尝试在一个平面文件上进行此操作。

SELECT username, count(*) as error_count 
FROM log 
WHERE error <> 0 
GROUP BY user_id WITH ROLLUP

别管表格是否规范化,使用平面文件做这件事将会更加缓慢和困难。
这真的是一件很容易的事情。


6

速度并非一切。是的,将日志写入文件的速度更快,但如果日志存储在数据库中,则您可以更快地找到所需的内容。几年前,我将我们的CMS从基于文件的日志转换为基于Mysql表的日志。使用表格更好。


3
这取决于日志的大小和并发级别。由于最新的情况,您的测试是完全无效的 - 如果网站上有100个用户,并且您有10个线程写入同一个文件,fwrite不会更快。关系型数据库管理系统提供的一项功能是并发控制。
这取决于要执行的要求和分析的种类。仅读取记录很容易,但是如何在定义的时间段内聚合一些数据呢?
大型网站使用像Scribe这样的系统来编写它们的日志。
然而,如果每分钟只有5条记录,这是非常低的负载,因此主要问题是如何读取它们。如果文件适合您的需求,请使用该文件。通常,追加写入(通常用于日志)非常快速。

2
我认为将日志存储在数据库中不是一个好主意。将日志存储到数据库中的优点是,您可以利用 SQL 的强大功能更轻松地分析日志;但是缺点是,您需要花费更多时间来维护数据库。您最好设置一个独立的数据库服务器来存储日志,否则可能会有太多的日志INSERT,这会降低生产使用的数据库性能;而且与文件(如 logrotate 等)相比,在数据库中迁移或归档日志不容易。
现在,您应该使用一些特殊的功能丰富的日志记录系统来处理您的日志,例如,logstash(http://logstash.net/)具有日志收集器、过滤器,并且可以将日志存储在外部系统(如 elasticsearch)中,结合美观的前端可视化和分析您的日志。
参考资料:

哦,应用程序出现了错误,哦,所有的错误都被记录在应用程序数据库中,哦,现在有更多的错误以及更多的数据在数据库中,噢,当错误发生时出现了递归,哇,当记录错误时,堆栈跟踪可以变得非常大,嗯,应用程序数据库已经没有太多空间了,哦,更多的错误甚至被记录下来,数据库空间很低,哦,数据库的空间用完了,哦,应用程序消失了 - 让我们检查日志,找出发生了什么事情,我们可以轻松地在数据库中访问它们.... - hakre

1

文件系统的写入速度应该总是更快。

然而,这不应该是您关心的问题。简单插入和写入文件系统都是快速操作。您需要担心的是当数据库崩溃时会发生什么。我个人喜欢同时写入两者,这样如果出现任何问题,就始终有日志可供查询,同时也可以从数据库中轻松搜索。


需要引用。我认为,如果你的数据库(引擎)支持行锁定,则将数据写入文件会导致每次写入时出现完全文件锁定,这样数据库可以更快。 - Johan
1
@Johan,文件中没有行。数据库将数据保存在文件中,而不是其他地方。 - Your Common Sense
@Col,数据库可以使用一些技巧来提高速度,例如使用内存引擎或将分区表分布在不同的磁盘上,以使插入速度比在平面文件中更快。我的观点是,文件系统并不总是更快。 - Johan
@Johan,我认为在大多数情况下,通过网络传输数据所需的时间比将其发送到文件中要长一个数量级。不过我必须承认我还没有进行过基准测试。 - Tom Squires

-1

据我看来,错误日志最好仅限于文件,因为如果数据库出现问题,您仍然可以记录下来。显然,如果错误日志需要连接到数据库,则无法选择此选项!

然而,我还要说的是,如果您正在进行大量的审计跟踪等记录工作,那么一般日志记录是我留在数据库中的。


-2

个人而言,我更喜欢日志文件,因此我创建了两个函数:

<?php
function logMessage($message=null, $filename=null)
{
    if (!is_null($filename))
    {
        $logMsg=date('Y/m/d H:i:s').": $message\n";
        error_log($logMsg, 3, $filename);
    }
}

function logError($message=null, $filename=null)
{
    if (!is_null($message))
    {
        logMessage("***ERROR*** {$message}", $filename);
    }
}
?>

我定义了一个或两个常量(我使用ACTIVITY_LOG和ERROR_LOG,都设置为相同的文件,这样您就不需要并排引用两个文件来获取运行的整体视图),并根据需要调用。我还创建了一个专用文件夹(/var/log/phplogs),每个我编写的应用程序都有自己的日志文件。最后,我会轮换日志,以便我可以参考一些历史记录给客户。

以上函数的自由使用意味着我可以相当容易地跟踪应用程序的执行。


因为我同时运行多个应用程序,不想在单个(可能很大的)日志文件中查找特定应用程序的错误。在任何人说“很多错误?码风不规范!”之前,我会访问很多有可能失败的外部服务,所以也需要记录日志。 - DaveyBoy
所以,您正在将PHP本地错误收集到单个(可能很大)的日志文件中,但手动错误会记录在较小的日志中。如果您问我,这是一种奇怪的设置。 - Your Common Sense
没有双重时间戳 - logError只是在传递的消息前面加上“ERROR”,然后将其传递给logMessage函数。最重要的是它对我有用,考虑到我是该平台唯一的系统管理员、开发人员、数据库管理员和支持人员,这比拥有一个记录所有内容的单个文件更重要,因为我必须搜索其中的细节以获取有关单个错误的详细信息。原始帖子是在问问题,我回答了我的想法并提供了几个函数。他是否采纳取决于他自己。个人选择才是最重要的。 - DaveyBoy
但是你已经在浏览每个应用程序并手动添加 $filename 了吗? - Your Common Sense
不,我根本没有这样说过。我建议您重新阅读我写的内容。此外,我会进行“测试”以找出所有PHP原生错误,然后才发布到生产环境。但是,“测试”无法考虑由外部影响引起的运行时错误。我想记录这些错误,以便在与远程方联系时可以参考它们。女士们先生们,这就是我的最后一条评论。对于原始帖子,很抱歉这篇文章劫持了您的问题。总的来说,答案是“做适合自己的事情,不要担心做错——如果需要,以后再改变它”。 - DaveyBoy
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接