データベースを扱うすべての開発者は、この問題を抱えています。また、x 年後にテーブルに含まれるレコード数を見積もることができないこともよくあります。
特に O/R マッパーで作業する場合、これは非常に不快です!
RDBMS ドライバーがこの問題を解決しないのはなぜですか? 一度ではなく複数回レコードを転送してから参照するのはなぜですか。クライアント アプリケーションの場合、これは完全に透過的です。または、高度な機能を提供することもできます。特に OR マッパーでは、DB データに類似したサブビーンを作成することは、参照としてのみ非常に役立つ場合があります。
冗長なデータを気にせずに 1:n テーブルを結合できれば素晴らしいことです。
このように最適化する RDBMS を知っている人はいますか? それとも、これはできませんか?もしそうなら、なぜですか?
----- ---- EDIT ----- -----
@Thilo: リンクありがとうございます。とても興味深い。
XAMPP for Windows でテストを実行しました。
PHP: 5.4.7
MySQL: 5.5.27
結果は、MySQL での JOIN に注意する必要があることを示しています。
JOIN を実行するたびに、重複したデータが得られます (1:1 を除く)。なぜこのデータを複数回転送するのですか?
テスト:
2 つのテーブルを作成しました。500 レコードと VARCHAR(32) を含む 9 列のテーブル A と、50000 レコードのテーブル B。(1:100)
SET @numA = 500;
SET @numBperA = 100;
DROP TABLE IF EXISTS `table_b`;
DROP TABLE IF EXISTS `table_a`;
DROP PROCEDURE IF EXISTS fill_table_b;
DROP PROCEDURE IF EXISTS fill_table_a;
CREATE TABLE `table_a` (
`id` int(11) NOT NULL,
`val1` varchar(32) NOT NULL,
`val2` varchar(32) NOT NULL,
`val3` varchar(32) NOT NULL,
`val4` varchar(32) NOT NULL,
`val5` varchar(32) NOT NULL,
`val6` varchar(32) NOT NULL,
`val7` varchar(32) NOT NULL,
`val8` varchar(32) NOT NULL,
`val9` varchar(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;
delimiter $$
CREATE PROCEDURE fill_table_a()
BEGIN
DECLARE i INT DEFAULT 1;
SET i = 1;
WHILE ( i <= @numA) DO
INSERT INTO table_a (id, val1, val2, val3, val4, val5, val6, val7, val8, val9)
VALUES (i, md5(rand()), md5(rand()), md5(rand()), md5(rand()), md5(rand()), md5(rand()), md5(rand()), md5(rand()), md5(rand()));
SET i=i+1;
END WHILE;
END$$
delimiter ;
call fill_table_a();
CREATE TABLE IF NOT EXISTS `table_b` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`table_a_id` int(11) NOT NULL,
`val` varchar(32) NOT NULL,
PRIMARY KEY (`id`),
KEY `table_a_id` (`table_a_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
ALTER TABLE `table_b` ADD CONSTRAINT `table_b_ibfk_1` FOREIGN KEY (`table_a_id`) REFERENCES `table_a` (`id`);
delimiter $$
CREATE PROCEDURE fill_table_b()
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE j INT DEFAULT 1;
SET i = 1;
WHILE (i <= @numA) DO
SET j = 1;
WHILE (j <= @numBperA) DO
INSERT INTO table_b (table_a_id, val)
VALUES (i, md5(rand()));
SET j=j+1;
END WHILE;
SET i=i+1;
END WHILE;
END$$
delimiter ;
call fill_table_b();
ここで、テーブル A から 300 行を選択し、テーブル B から依存する 30000 行を選択します。
私はこれを3つの方法で行いました:
単一のリクエストで A JOIN B を選択
$time = microtime(true);
for( $i = 0; $i < 50; $i++ ) {
$resultA = mysqli_query($link, "SELECT * FROM table_a LEFT JOIN table_b ON table_b.table_a_id = table_a.id WHERE table_a.id BETWEEN 100 AND 399");
$resultArray = array();
//while( $resultArray[] = mysqli_fetch_assoc($resultA) ) {}
$numRows = mysqli_num_rows($resultA);
}
$time2 = microtime(true);
echo("numSelectedRows: " . $numRows . "<br>time: " . number_format($time2 - $time, 3) . " sec.<br>Memory: " . number_format(memory_get_peak_usage() / 1024 / 1024, 3) . " MiB");
fetch
numSelectedRows: 30000
時間: 15.539 秒。
メモリ: 55.649 MiBフェッチなし
numSelectedRows: 30000
時間: 6.262 秒。
メモリ: 3.431 MiB
単一の要求で A を選択します。Result を反復処理し、テーブル B に対して 300 のリクエストを行います。
$time = microtime(true);
for( $i = 0; $i < 50; $i++ ) {
$numRowsB = 0;
$resultA = mysqli_query($link, "SELECT * FROM table_a WHERE table_a.id BETWEEN 100 AND 399");
while( $row = mysqli_fetch_assoc($resultA) ) {
$resultB = mysqli_query($link, "SELECT * FROM table_b WHERE table_b.table_a_id = " . $row['id']);
while( mysqli_fetch_assoc($resultB) ) {}
$numRowsB += mysqli_num_rows($resultB);
}
}
$numRowsA = mysqli_num_rows($resultA);
$time2 = microtime(true);
echo("numSelectedRows A: " . $numRowsA . "<br>numSelectedRows B: " . $numRowsB . "<br>time: " . number_format($time2 - $time, 3) . " sec.<br>Memory: " . number_format(memory_get_peak_usage() / 1024 / 1024, 3) . " MiB");
- fetch
numSelectedRows A: 300
numSelectedRows B: 30000
時間: 7.713 秒。
メモリ: 0.364 MiB
単一の要求で A を選択します。単一の要求で B を選択します。
$time = microtime(true);
for( $i = 0; $i < 50; $i++ ) {
$resultA = mysqli_query($link, "SELECT * FROM table_a WHERE table_a.id BETWEEN 100 AND 399");
$resultB = mysqli_query($link, "SELECT * FROM table_b WHERE table_b.table_a_id BETWEEN 100 AND 399");
$resultArray = array();
//while( $resultArray[] = mysqli_fetch_assoc($resultA) ) {}
//while( $resultArray[] = mysqli_fetch_assoc($resultB) ) {}
}
$numRowsA = mysqli_num_rows($resultA);
$numRowsB = mysqli_num_rows($resultB);
$time2 = microtime(true);
echo("numSelectedRows A: " . $numRowsA . "<br>numSelectedRows B: " . $numRowsB . "<br>time: " . number_format($time2 - $time, 3) . " sec.<br>Memory: " . number_format(memory_get_peak_usage() / 1024 / 1024, 3) . " MiB");
fetch
numSelectedRows A: 300
numSelectedRows B: 30000
時間: 6.020 秒。
メモリ: 15.928 MiBフェッチなし
numSelectedRows A: 300
numSelectedRows B: 30000
時間: 3.018 秒。
メモリ: 1.156 MiB