1

在我的项目中,我经常需要将 SELECT 的结果存储在另一个表中(我们称之为“结果集”)。原因是在 Web 应用程序中动态显示大量行,同时根据需要仅加载小块。

通常,这是通过以下查询完成的:

SET @counter := 0;
INSERT INTO resultsetdata
  SELECT "12345", @counter:=@counter+1, a.ID
    FROM sometable a
    JOIN bigtable b
      WHERE (a.foo = b.bar)
    ORDER BY a.whatever DESC;

固定"12345"值只是将“结果集”标识为一个整体的值,并针对每个查询进行更改。第二列是递增索引计数器,旨在允许直接访问结果中的特定行,并且该ID列引用源数据表中的特定行。

当应用程序需要一定范围的结果时,我只需加入resultsetdata源表即可获取详细数据——这与resultsetdata上面的查询相反,后者可能需要 2-3 秒才能完成(这解释了为什么我需要这个中间表)。

SELECT 查询本身与此问题无关。

resultsetdata具有以下结构:

CREATE TABLE `resultsetdata` (
  `ID`      int(11) NOT NULL,
  `ContIdx` int(11) NOT NULL,
  `Value`   int(11) NOT NULL,
  PRIMARY KEY (`ID`,`ContIdx`)
) ENGINE=InnoDB;

这通常就像一个魅力,但最近我们注意到在某些情况下结果的 ORDER 是不正确的。这取决于查询本身(例如,添加DISTINCT是一个典型的原因)、服务器版本和源表中包含的数据,所以我猜可以说使用这种方法无法预测行顺序。可能它取决于内部优化。

但是,现在的问题是我想不出任何可以给我预期结果的替代解决方案。

由于结果集可以得到几千行,因此将所有数据加载到内存中然后手动INSERT读取它是不可行的。

有什么建议么?


编辑:为了进一步澄清,看看这些查询:

DROP TABLE IF EXISTS test;
CREATE TABLE test (ID INT NOT NULL, PRIMARY KEY(ID)) ENGINE=InnoDB;
INSERT INTO test (ID) VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);

SET @counter:=0;
SELECT "12345", @counter:=@counter+1, ID
  FROM test
  ORDER BY ID DESC;

这会产生以下“预期”结果:

+-------+----------------------+----+
| 12345 | @counter:=@counter+1 | ID |
+-------+----------------------+----+
| 12345 |                    1 | 10 |
| 12345 |                    2 |  9 |
| 12345 |                    3 |  8 |
| 12345 |                    4 |  7 |
| 12345 |                    5 |  6 |
| 12345 |                    6 |  5 |
| 12345 |                    7 |  4 |
| 12345 |                    8 |  3 |
| 12345 |                    9 |  2 |
| 12345 |                   10 |  1 |
+-------+----------------------+----+
10 rows in set (0.00 sec)

如前所述,在某些情况下(我无法在此处提供测试用例,抱歉),这可能会导致类似于以下的结果:

+-------+----------------------+----+
| 12345 | @counter:=@counter+1 | ID |
+-------+----------------------+----+
| 12345 |                   10 | 10 |
| 12345 |                    9 |  9 |
| 12345 |                    8 |  8 |
| 12345 |                    7 |  7 |
| 12345 |                    6 |  6 |
| 12345 |                    5 |  5 |
| 12345 |                    4 |  4 |
| 12345 |                    3 |  3 |
| 12345 |                    2 |  2 |
| 12345 |                    1 |  1 |
+-------+----------------------+----+

我并不是说这是一个 MySQL 错误,我完全理解我的方法目前提供了不可预测的结果。不过,我不知道如何调整它以获得可预测的结果。

4

2 回答 2

3

这是因为记录插入时的排序顺序与检索它们时的顺序无关。

当您检索它们时,将创建一个查询计划。如果您的 SELECT 语句中未指定 ORDER BY,则顺序将取决于生成的查询计划。这就是为什么它是不可预测的并且添加 DISTINCT 可以改变顺序。

解决方案是存储足够的数据,以便您可以使用 ORDER BY 子句以正确的顺序检索它们。在您的情况下,您已经通过 a.whatever 订购了您的数据。a.whatever 可以存储在结果集数据中吗?如果是这样,那么您可以按照正确的顺序读出记录。

于 2013-04-10T08:46:05.870 回答
1

也许您可以将选择包装到另一个选择中:

SET @counter := 0;

INSERT INTO resultsetdata
  SELECT *, @counter := @counter + 1
  FROM (
      SELECT "12345", a.ID
      FROM sometable a
      JOIN bigtable  b
      WHERE a.foo = b.bar
      ORDER BY a.whatever DESC
  ) AS tmp

...但是您仍然受制于 MySQL 优化器的愚蠢。

这就是我发现的关于这个主题的全部内容,但我找不到一个硬性保证:

结果集中行自动编号的纯 SQL 技术

http://www.xaprb.com/blog/2006/12/02/how-to-number-rows-in-mysql/

http://www.xaprb.com/blog/2005/09/27/simulating-the-sql-row_number-function/

于 2013-04-10T09:25:16.013 回答