[pgsql-jp: 36832] 別テーブルを条件にしたUPDATEの性能(Re: WHEREにSELECTふたつ)

河本陽一 komoto.yoichi @ kcc.co.jp
2006年 3月 7日 (火) 13:11:31 JST


こうもとです。

ISHIDA Akio さんの書いたこと:
> > # UPDATE t1 SET f3=f3+1 where (f1)=(SELECT f1 FROM t2 WHERE f5='a02');
> > UPDATE 2
> 
> いや、kuniyoshi さんがおっしゃっているのは、
> SELECT f1 FROM t2 WHERE f5='a02'
> が複数の行を返すケースですよね。

 あ、いわれてみるとそのとおりですね。
 WHEREが複数と勘違いしていました。



 やりたいことの解決方法に2通りの方法があったので、どちらを使えばよ
いかの判断のためにベンチマークをとってみました。
 t1に900件、t2に27000件のレコードを入力し、ランダムに500回のUPDATE
を10回行いました。
#テーブル構成は変更ありません。

案1(PostgreSQL専用):根岸さんと、海北さんの方法
UPDATE t1 SET f3=f3+1
    FROM t2 WHERE t1.f1=t2.f1 AND t1.f2=t2.f2 AND t2.f5='a01';

案2(汎用):石田さんの方法
UPDATE t1 SET f3=f3+1
    WHERE (f1,f2)=(SELECT f1,f2 FROM t2 WHERE f5='a01');

 結果は、圧倒的に案1の方早いことがわかりました。

----------------------------------------------------------------------
$ ./benchdb.pl bench
bench1:11 wallclock secs ( 0.00 usr +  0.23 sys =  0.23 CPU) @ 21.74/s (n=5)
bench2:1316 wallclock secs ( 0.04 usr +  0.36 sys =  0.40 CPU) @ 12.50/s (n=5)
RET=[0]
----------------------------------------------------------------------

環境:
OS:Debian sarge (coLinux上)
DB:PostgreSQL 7.4.7

#テストに使用したソースはメールの最後に貼りました。

 案2が異常に遅いのですが、何かインデックスが必要なのでしょうか。
 案2のログの処理時間を見ると、1回で500ms程度もかかっていました。

----------------------------------------------------------------------
statement: UPDATE t1 SET f3=f3+1 WHERE (f1,f2)=(SELECT f1,f2 FROM t2 WHERE f5='text00019780');
duration: 509.891 ms
statement: UPDATE t1 SET f3=f3+1 WHERE (f1,f2)=(SELECT f1,f2 FROM t2 WHERE f5='text00019780');
duration: 606.183 ms
statement: UPDATE t1 SET f3=f3+1 WHERE (f1,f2)=(SELECT f1,f2 FROM t2 WHERE f5='text00019780');
duration: 520.638 ms
----------------------------------------------------------------------


 切り分けのため、部分的に実行してみたつもりでしたが、どれもそれほど
時間はかかっていません。
#ばらつきは大きいですが。
----------------------------------------------------------------------
statement: SELECT f1,f2 FROM t2 WHERE f5='text00019780';
duration: 0.487 ms
statement: SELECT f1,f2 FROM t2 WHERE f5='text00019780';
duration: 0.488 ms
statement: SELECT f1,f2 FROM t2 WHERE f5='text00019780';
duration: 0.489 ms
statement: UPDATE t1 SET f3=f3+1 WHERE f1=21 AND f2=29;
duration: 31.646 ms
statement: UPDATE t1 SET f3=f3+1 WHERE f1=21 AND f2=29;
duration: 14.880 ms
statement: UPDATE t1 SET f3=f3+1 WHERE f1=21 AND f2=29;
duration: 0.833 ms
statement: UPDATE t1 SET f3=f3+1 WHERE (f1,f2)=(21,29);
duration: 20.654 ms
statement: UPDATE t1 SET f3=f3+1 WHERE (f1,f2)=(21,29);
duration: 0.825 ms
statement: UPDATE t1 SET f3=f3+1 WHERE (f1,f2)=(21,29);
duration: 31.404 ms
----------------------------------------------------------------------

 なぜこれほど処理時間に違いが出てくるのでしょうか。


======================================================================
河本陽一(こうもとよういち)
mailto:komoto.yoichi @ kcc.co.jp


test.sql
----------------------------------------------------------------------
DROP TABLE t2;
DROP TABLE t1;

CREATE TABLE t1 (
	f1		int,
	f2		int,
	f3		int,
	PRIMARY KEY (f1, f2)
);
GRANT SELECT,INSERT,UPDATE,DELETE ON t1 TO youichi;

CREATE TABLE t2 (
	f1		int,
	f2		int,
	f4		int,
	f5		text UNIQUE,
	FOREIGN KEY (f1, f2) REFERENCES t1,
	PRIMARY KEY (f1, f2, f4)
);
GRANT SELECT,INSERT,UPDATE,DELETE ON t2 TO youichi;
----------------------------------------------------------------------

benchdb.pl
----------------------------------------------------------------------
#!/usr/bin/perl

use strict;
use Benchmark;
use Pg;

my $DBNAME			= 'test';
my $DBMAX1			= 30;
my $DBMAX2			= 30;
my $DBMAX3			= 30;
my $BLOOP			= 500;
my $FMT_VAL			= 'text%08d';

&main($ARGV[0]);

#############################################################################
sub main($)
{
	my($mode) = @_;
	my $ret;

	if($mode eq 'make'){
		$ret = &make();
	}
	elsif($mode eq 'bench'){
		timeit(1, sub {&bench(1)}); #準備運動
		my $time;
		$time = timeit(5, sub {&bench(1)});
		print("bench1:".timestr($time)."\n");
		$time = timeit(5, sub {&bench(2)});
		print("bench2:".timestr($time)."\n");
		$ret = 0;
	}
	else{
		$ret = "No option";
	}

	print "RET=[$ret]\n";
}

#############################################################################
sub make()
{
	my $sql;

	my $db = Pg::connectdb("dbname=$DBNAME");
	if($db->status != PGRES_CONNECTION_OK){
		return 1;
	}
	
	my $cnt = 0;
	for(my $i = 0; $i < $DBMAX1; $i++){
		for(my $j = 0; $j < $DBMAX2; $j++){
			if(&SqlExe($db, "INSERT INTO t1 VALUES ($i, $j, 0)")){
				return 1;
			}
			for(my $k = 0; $k < $DBMAX3; $k++){
				my $val = sprintf($FMT_VAL, $cnt);
				$cnt++;
				if(&SqlExe($db, "INSERT INTO t2 VALUES ($i, $j, $k,'$val')")){
					return 1;
				}
			}
		}
	}
	printf("db=%d.\n", $DBMAX1 * $DBMAX2 * $DBMAX3);

	return 0;
}

#############################################################################
sub bench()
{
	my($type) = @_;

	my $db = Pg::connectdb("dbname=$DBNAME");
	if($db->status != PGRES_CONNECTION_OK){
		print "db open error.\n";
		return 1;
	}

	my $sqlfmt = "UPDATE t1 SET f3=f3+1 ";
	if($type == 1){
		$sqlfmt .= "FROM t2 WHERE t1.f1=t2.f1 AND t1.f2=t2.f2 AND t2.f5='%s'";
	}
	elsif($type == 2){
		$sqlfmt .= "WHERE (f1,f2)=(SELECT f1,f2 FROM t2 WHERE f5='%s')";
	}
	else{
		return 1;
	}
#	srand(12345);
	my $max = $DBMAX1 * $DBMAX2 * $DBMAX3;
	for(my $i = 0; $i < $BLOOP; $i++){
		my $val = sprintf($FMT_VAL, int(rand() * $max));
		if(&SqlExe($db, sprintf($sqlfmt, $val))){
			return 1;
		}
	}

	return 0;
}

#############################################################################
sub SqlExe($$)
{
	my($db,$sql) = @_;

	my $res = $db->exec($sql);
	if($res->resultStatus == PGRES_COMMAND_OK){
		return 0;
	}
	print $db->errorMessage;
	return 1;
}
----------------------------------------------------------------------





pgsql-jp メーリングリストの案内