LoginSignup
1
0

More than 5 years have passed since last update.

Limit で制限する前提の無限再帰 CTE は MySQL では動かない。 Posgresql なら動く。

Posted at

TL; DR

  • MySQL の再帰 CTE は、一回その結果をすべて materialize している
    • なので、クエリのパフォーマンスは注意が必要
  • Postgresql ならば、幅優先探索ができるはず。

本文

実証:

MySQL

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.11    |
+-----------+
1 row in set (0.00 sec)

mysql> with recursive r as ( select 1 as n UNION ALL select n + 1 from r ) select * from r limit 10;
ERROR 3636 (HY000): Recursive query aborted after 1001 iterations. Try increasing @@cte_max_recursion_depth to a larger value.

Postgresql

test=# select version();
                                                    version                                                    
---------------------------------------------------------------------------------------------------------------
 PostgreSQL 10.5 on x86_64-apple-darwin17.7.0, compiled by Apple LLVM version 9.1.0 (clang-902.0.39.2), 64-bit
(1 row)

test=# with recursive r as ( select 1 as n UNION ALL select n + 1 from r ) select * from r limit 10;
 n  
----
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
(10 rows)

理由

Postgresql は、そのセレクトのクエリにおいては、クエリグラフのようなものを用意し、必要な分だけ計算する。 Haskell 的なノリで、 fetch by need する。おそらくそれが効いている。

MySQL は、以下の explain を見ていると分かるけれども、

mysql> explain with recursive r as ( select 1 as n UNION ALL select n + 1 from r ) select * from r limit 10;                            
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+----------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | NULL           |
|  2 | DERIVED     | NULL       | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | No tables used |
|  3 | UNION       | r          | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | Recursive      |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+----------------+
3 rows in set, 1 warning (0.00 sec)

cte の結果のテーブルが導出されて、そこから PRIMARY の駆動が ALL で走っている。ちなみに、id=3 の駆動の type=ALL は、少なくとも公式ドキュメントを見る限りでは、1回前で新しく追加された rows に対しての ALL を表している様子。

MySQL は、 join にならないかぎり、ある駆動表が依存するテーブルたちは、一度中間テーブルとして全体が実体化される。今回は cte のテーブルを実体化している最中に再帰の上限値をオーバーした。

何が言えるか

MySQL で再帰CTE を使う場合は、それは何かしらの(条件付き)閉包を計算したくて、かつ、その結果自体がほしかったものである場合、においてのみ使うとよさそう。具体的には子孫列挙とか。

postgres の性質を活かすと、たとえばグラフ構造をデータベースに突っ込んで、幅優先探索を再帰 CTE 、でできると思われる。where finished = true limit 1 で終了条件として、 CTE を組めば多分これができる。今回の数列の select を、 limit 1 offset 10000000 とかにしてみても、利用するリソースは CPU だけだったので、多分効率的に探索が組めるはず。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0