2

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

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

你好,

我目前正在从事一个旨在数据老化方向的项目。即,我们试图确定哪些元组被定期访问,哪些不是。我们有一个体面的工作负载(即系统的查询日志)以及相应的数据,并且想知道哪些行已被处理。

除了问题之外,我们还对哪些属性感兴趣,但这可以通过解析查询(投影、连接属性和 where 条件)来完成。让问题悬而未决,如何获取实际处理的行。

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

我现在的最后一个问题是:我们如何才能做到这一点?

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

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

4

4 回答 4

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.c和的两个文件,这两个文件nodeIndexscan.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.c对函数执行相同的操作IndexNext

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

现在,这个修改后的版本将在它处理的每个元组上引发一个 NOTICE 消息seqscanor indexscan(并且只有那些)。您可以使用函数调用修改该行elog以执行您想要的任何操作。

玩得开心。

于 2013-09-24T18:38:12.550 回答
2

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

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 行,您会看到下面的三行:“Rows Removed by Filter: 985”,这意味着它忽略(但已处理)985 行,因此您已985+15=1000扫描。

要实际查看已处理的行,我只能考虑一种(一种 hacky)解决方案,该解决方案创建一个虚拟函数,该函数只会RAISE NOTICE/LOG/DEBUG从已处理的行发送一个值,甚至填充一个临时表(我认为这更好),并在WHERE子句上调用此函数。这样做的问题是 PostgreSQL 的规划器可能会重新排序ANDs 的执行而不是首先执行函数调用。我们可以尝试将函数的 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以获得有效的解决方案。

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

于 2013-09-14T15:03:32.730 回答
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 提供已处理的行列表。

于 2013-10-28T20:17:10.667 回答
0

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

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

您可以使用位于程序领域的文件系统来执行您建议的数据老化查询。许多文件系统对每个文件都有一个最近引用的日期属性。但 RDBMS 并非如此。

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

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

您可以戳破 RDBMS 上的声明性面纱,并使用诸如EXPLAIN获取 RDBMS 之类的技术来为您提供有关其内部过程的提示。您已经发现了这种方法的局限性。

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

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

于 2013-09-14T13:27:39.313 回答