0

データベースを扱うすべての開発者は、この問題を抱えています。また、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

4

2 に答える 2