每个使用数据库的开发人员都有这个问题。 通常,您无法估计表格在x年内的记录数量。
特别是在使用O / R映射器时,这非常不舒服!
为什么RDBMS驱动程序无法解决此问题? 为什么多次传输记录而不是一次,然后引用它。 对于客户端应用程序,这可能是完全透明的。甚至提供高级功能。 特别是使用OR映射器,创建类似于DB数据的子类甚至可能非常有用,仅作为参考。
如果你能加入1:n表而不知道还原剂数据,那就太棒了。
有谁知道像这样优化的RDBMS? 或者不能这样做?如果是这样,为什么?
----- ----编辑----- -----
@Thilo:谢谢你的链接。非常有趣。
我使用XAMPP for Windows进行测试
PHP:5.4.7
MySQL:5.5.27
结果表明你必须小心MySQL中的JOIN。
每次进行加入时,您都会获得重复数据(1:1除外)。为什么要多次传输这些数据?
测试
我创建了两个表。表A包含500条记录,9列包含VARCHAR(32),表B包含50000条记录。 (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.迭代结果并向表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");
带有提取
numSelectedRows A:300
numSelectedRows B:30000
时间:6.020秒 记忆:15.928 MiB没有抓取 numSelectedRows A:300
numSelectedRows B:30000
时间:3.018秒 记忆:1.156 MiB
答案 0 :(得分:2)
为什么RDBMS驱动程序无法解决此问题?
出于同样的原因,查询优化器有时也无法正确使用它。
这很难。
数据库(和其他软件)尽力优化查询执行,但有时您仍然需要手动“帮助它”。
如果有的话,我更喜欢只有数据库本身才能在这里尝试,而不是让其他层(例如OR / mapper或数据库驱动程序)也在“自动化”。否则,该过程将变得完全不可预测,并且在必要时难以操纵。
答案 1 :(得分:1)
因为RDBMS驱动程序不返回结构化实体而是返回通用数据集,并且无法知道返回的数据字段是如何相互关联的:是应该在应用程序中保留的一些相关行的计数,因为它有点命名在查询中?
如果这么容易,有人已经发现了宇宙的奥秘,我很乐意签署表格并退休! ;))
遗憾的是,您需要做什么查询取决于架构,数据重新分区以及最后但不是最不重要的业务规则和应用程序功能。所以在某些情况下,你需要有或没有分组的连接,否则多个查询会更好,所以就这样吧。