简而言之:如何获得处理查询时处理的行的行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帖子几乎从未观看,喜欢,评论,因此可以存储在外部(更便宜)的系统上。因此,我们现在需要定期访问哪些行。
答案 0 :(得分: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行,你会看到三行:“过滤器删除的行: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中执行以下操作。
首先,一些注意事项:
现在,我们的想法是,首先创建一个将元组转储到字符串的函数。为了方便起见,我创建了一个名为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
现在,这个修改后的版本将在它使用seqscan
或indexscan
处理的每个元组上引发一条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提供已处理的行的列表。