繁体   English   中英

SQL JOIN与多个SELECT语句

[英]SQL JOIN vs. multiple SELECT statements

每个使用数据库的开发人员都会遇到此问题。 通常,您无法估计一张表在x年内将拥有多少条记录。

尤其是在使用O / R映射器时,这非常不舒服!

为什么RDBMS驱动程序不能解决此问题? 为什么要多次传输一条记录而不是一次,然后再引用它。 对于客户端应用程序,这可能是完全透明的。 甚至提供高级功能。 特别是对于OR映射器,仅作为参考来创建类似于DB数据的子bean甚至可能非常有用。

如果您可以在不知道冗余数据的情况下加入1:n表,那将是极好的。

有谁知道像这样优化的RDBMS? 还是不能做到这一点? 如果是这样,为什么?

----- ----编辑----- -----
@Thilo:感谢您的链接。 很有意思。

我已经使用Windows XAMPP进行了测试。
的PHP:5.4.7
的MySQL:5.5.27
结果表明您必须谨慎使用MySQL中的JOIN。

每次执行JOIN操作时,您将获得重复的数据(1:1除外)。 为什么要多次传输此数据?

测试:

我创建了两个表。 具有500条记录的表A和具有VARCHAR(32)的9列以及具有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");
  • 与获取
    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");
  • 与获取
    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");
  • 与获取
    numSelectedRows A:300
    numSelectedRows B:30000
    时间:6.020秒。
    内存:15.928 MiB

  • 没有获取
    numSelectedRows A:300
    numSelectedRows B:30000
    时间:3.018秒。
    内存:1.156 MiB

为什么RDBMS驱动程序不能解决此问题?

出于同样的原因,查询优化器有时也无法正确执行。

这很难。

数据库(和其他软件)尽最大努力优化查询执行,但是有时您仍然需要手动“帮助”。

如果有的话,我宁愿只希望数据库本身甚至在这里尝试一下它的手,而不希望其他层(例如OR / mapper或数据库驱动程序)也“自动”摆弄。 否则,该过程将变得完全不可预测,并且在必要时将很难控制。

因为RDBMS驱动程序不返回结构化实体而是通用数据集,并且没有办法知道返回的数据字段如何相互关联:某些相关行的计数要保留在应用程序中,仅仅是因为它在查询中有所命名?

如此简单,有人已经发现了宇宙的奥秘,我很乐意在表格上签字并退休! ;))

可悲的是,您需要执行的查询取决于架构,数据分区以及最后但并非最不重要的业务规则和应用程序功能。 因此,在某些情况下,您将需要具有或不具有分组的联接,否则多个查询会更好。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM