如何确定查询的已处理数据库表行?

时间:2013-09-14 12:36:45

标签: mysql sql database postgresql analytics

简而言之:如何获得处理查询时处理的行的行ID列表?

编辑注释:我不是在寻找返回的行。当用户在Facebook上有5个帖子并且我做了'''SELECT * FROM帖子WHERE user = Mark ORDER BY date desc LIMIT 1'''我知道,返回的号码将是1,但我想知道已经处理了多少行(在这种情况下没有索引,可能是所有行)。我主要关注SELECT语句。

您好,

我目前正致力于一个旨在推动数据老化的项目。即,我们正在尝试确定哪些元组是定期访问的,哪些不是。 我们有一个不错的工作负载(即系统的查询日志)和相应的数据,并想知道哪些行已被处理。

除了问题,我们还对哪些行感兴趣,但是可以解析查询(投影,连接属性和条件)。 让问题打开如何获得实际处理的行。

我们知道很多查询会(让我们假设没有索引)处理所有行,因为有一个需要全表扫描的where-condition。 我们知道这个问题,但仍然不知道哪些行已被访问过。

我现在的最后一个问题是:我们如何实现这一目标?

我一直在研究MySQL和Postgres,但找不到足够的信息(例如,MySQL的'explain'只返回对已处理行数的估计,但不返回任何行ID)。我猜我们将不得不修改数据库的源代码以实现这种日志记录(日志记录的性能不是问题,它是离线分析)。 有没有人建议如何实现这一目标?

编辑有关David的评论:我想要实现的是知道,从未访问过哪些元组(查看给定的工作负载)。典型的老化问题。例如,超过2年的Facebook帖子几乎从未观看,喜欢,评论,因此可以存储在外部(更便宜)的系统上。因此,我们现在需要定期访问哪些行。

4 个答案:

答案 0 :(得分:2)

我不能说任何关于MySQL的内容,但是使用PostgreSQL可以使用EXPLAIN (ANALYZE,VERBOSE)VERBOSE选项将为您提供已处理的行数。有关工作案例,请参阅此SQL FiddleEXPLAIN的输出为:

Limit (cost=17.57..17.58 rows=1 width=8) (actual time=0.145..0.146 rows=1 loops=1)
Output: a, b
-> Sort (cost=17.57..17.61 rows=15 width=8) (actual time=0.143..0.143 rows=1 loops=1)
    Output: a, b
    Sort Key: foo.a
    Sort Method: top-N heapsort Memory: 25kB
    -> Seq Scan on public.foo (cost=0.00..17.50 rows=15 width=8) (actual time=0.014..0.114 rows=15 loops=1)
        Output: a, b
        Filter: (foo.b = 1)
        Rows Removed by Filter: 985
        Total runtime: 0.168 ms

如果你查看Seq Scan节点,你可以看到它返回了15行,你会看到三行:“过滤器删除的行:985”,这意味着它忽略(但已处理)985行,所以你扫描了985+15=1000

要实际查看已处理的行,我只能考虑创建一个虚拟函数的(一种hacky)解决方案,该函数只会从已处理的行发送RAISE NOTICE/LOG/DEBUG值,甚至可以填充临时表(我认为这样更好),并在WHERE子句上调用此函数。这个问题是PostgreSQL的计划程序可能会重新排序AND的执行,而不是先执行函数调用。我们可以尝试将函数的COST设置为1,但不能保证这将始终工作。功能是:

CREATE OR REPLACE FUNCTION logit(v anyelement)
RETURNS BOOLEAN
LANGUAGE PLPGSQL AS $$
BEGIN
    INSERT INTO tmp_row_process_log VALUES(v);
    RETURN TRUE;
END;
$$
COST 1;

使用:

CREATE TEMP TABLE tmp_row_process_log(a int);

SELECT * FROM foo
WHERE logit(a) AND b = 1
ORDER BY a
LIMIT 1;

SELECT * FROM tmp_row_process_log;

查看此SQL Fiddle以获得有效的解决方案。

请注意,使用此解决方案,您实际上可以更改计划程序的决策,因此使用函数调用时可能不一样。您可以使用这两种解决方案并比较结果。

答案 1 :(得分:2)

如果您真的愿意调整源代码,可以在PostgreSQL中执行以下操作。

首先,一些注意事项:

  • 这样做非常糟糕的主意,它仅用于教育目的。首先,因为它会减慢速度,其次是因为它可能会使服务器崩溃(我没有正确测试)
  • 我在PostgreSQL 9.2上尝试了以下内容,它应该与其他版本一起使用,我只是没试过。

现在,我们的想法是,首先创建一个将元组转储到字符串的函数。为了方便起见,我创建了一个名为src/include/debugtuple.h的文件(在pg源代码内),其中包含以下内容:

#ifndef _DEBUGTUPLE_H_
#define _DEBUGTUPLE_H_

#include "postgres.h"

#include "access/relscan.h"
#include "executor/execdebug.h"
#include "utils/rel.h"

static void InitScanRelation(SeqScanState *node, EState *estate);
static TupleTableSlot *SeqNext(SeqScanState *node);

static char *
ExecBuildSlotValueDescription(TupleTableSlot *slot, int maxfieldlen)
{
    StringInfoData buf;
    TupleDesc   tupdesc = slot->tts_tupleDescriptor;
    int         i;

    /* Make sure the tuple is fully deconstructed */
    slot_getallattrs(slot);

    initStringInfo(&buf);

    appendStringInfoChar(&buf, '(');

    for (i = 0; i < tupdesc->natts; i++)
    {
        char       *val;
        int         vallen;

        if (slot->tts_isnull[i])
            val = "null";
        else
        {
            Oid         foutoid;
            bool        typisvarlena;

            getTypeOutputInfo(tupdesc->attrs[i]->atttypid,
                              &foutoid, &typisvarlena);
            val = OidOutputFunctionCall(foutoid, slot->tts_values[i]);
        }

        if (i > 0)
            appendStringInfoString(&buf, ", ");

        /* truncate if needed */
        vallen = strlen(val);
        if (vallen <= maxfieldlen)
            appendStringInfoString(&buf, val);
        else
        {
            vallen = pg_mbcliplen(val, vallen, maxfieldlen);
            appendBinaryStringInfo(&buf, val, vallen);
            appendStringInfoString(&buf, "...");
        }
    }

    appendStringInfoChar(&buf, ')');

    return buf.data;
}

#endif

现在,您必须编辑位于src/backend/executor/的两个文件,nodeSeqscan.cnodeIndexscan.c两个文件,包含上面创建的文件,包含以下内容(在{文件):

#include "debugtuple.h"

nodeSeqscan.c,找到SeqNext函数并对其进行编辑以匹配以下内容(仅添加这两行):

static TupleTableSlot *
SeqNext(SeqScanState *node)
{
    [...]
        ExecClearTuple(slot);

    /* ADD THE TWO FOLLOWING LINES: */
    if (slot && slot->tts_tuple)
        elog(NOTICE, "Seq Scan processed: %s", ExecBuildSlotValueDescription(slot, 1000));
    return slot;
}

现在,使用函数nodeIndexscan.cIndexNext执行相同操作:

static TupleTableSlot *
IndexNext(IndexScanState *node)
{
    [...]
    while ((tuple = index_getnext(scandesc, direction)) != NULL)
    {
        [...]

        /* ADD THE TWO FOLLOWING LINES: */
        if (slot && slot->tts_tuple)
            elog(NOTICE, "Index Scan processed: %s", ExecBuildSlotValueDescription(slot, 1000));
        return slot;
    }

    ...
    return ExecClearTuple(slot);
}

最后,转到源代码的根目录并重新编译它:

make && make install

现在,这个修改后的版本将在它使用seqscanindexscan处理的每个元组上引发一条NOTICE消息(仅限那些)。您可以使用elog函数调用修改该行以执行任何操作。

玩得开心。

答案 2 :(得分:0)

这是一个有趣的问题,因为它揭示了关系数据库技术的基本属性。

SQL不是一种过程语言,它是一种声明性语言。将它与客户端过程语言(如Java,C#,php)一起使用会使您在声明和过程之间处于一个奇怪的境界。由于现实生活中的RDBMS以程序化方式实现,因此您所居住的领域更加陌生。因此,当编写应用程序SQL时,你生活在一层夹着两层程序性岩石的声明性煤之中。

您可以使用文件系统执行此数据老化查询,该文件系统位于程序领域。许多文件系统都具有每个文件最近引用日期的属性。但是RDBMS并不是这样。

当您执行SELECT操作时,您声明特定表,特定的数据行序列,每行包含某些列。这有时称为结果集。此表基于RDBMS中其他表的内容。

请原谅我在这里是一个纯粹主义者,但你问一个程序问题 - 你检索了什么存储数据以及什么时候? - 声明系统。这个问题毫无意义,在声明领域没有答案。理解这一点很重要,因为它反映了RDBMS的构建方式。

您可以在RDBMS上穿透声明面纱,并使用EXPLAIN等技术让RDBMS为您提供有关其内部过程的提示。你已经发现了这种方法的局限性。

您可以在应用程序中添加程序性内容,在其中标记您处理的每一行,可能使用date_processed列。完成后,您可以使用SQL声明一个结果集,该结果集显示数据的相关性老化。

或者您可以找出另一种声明相关性的方法,可能是基于已经有date_created列或类似的记录的老化。

答案 3 :(得分:0)

我可能会迟到一点。但根据您的问题,这可以在Postgresql中轻松完成

rahul=# select count(*) , array_agg(col1) from nametest where year > 1990 limit 2 ;
 count | array_agg 
-------+-----------
     3 | {5,6,7}
(1 row)

array_agg提供已处理的行的列表。