Yii2データベースにツリーを保存して共有するための動作セット



こんにちは、Habr!



Yii2プロジェクトの1つで、隣接リストとネストされたセットを組み合わせたいと思いました。 そのため、ネストされたセットの動作を無効にしても、機能は完全に機能し続けます。 その後、データベースにフルパスを保存する必要があるため、ネストセットは不要であることに気付き、マテリアライズドパスを置換に使用することにしました。 GitHubで利用可能な動作( matperez / yii2-materialized-path )は十分に機能していなかったため、独自の動作を作成する必要がありました。 API、およびそれらをモデルに同時に任意に接続し、それぞれを活用する機能。




行動リスト



隣接リスト



+自立した完全性構造

+子孫の再カウントと更新を必要としないため、迅速な修正

+直近の親子の迅速な受け取り

-すべての祖先と子孫を取得する複雑さと遅さ



最も単純な形式の通信は、ほとんどの場合、その実装では動作を接続せず、親と子との関係を登録するだけです。 ただし、隣接リストにノードの並べ替えフィールド( insertBefore()



/ insertAfter()



)を追加する価値はあります。その場合、既成の動作がなければ困難になります。 はい、そして1つの関係のすべての祖先/子孫を取得することはすでに困難です。

これらの問題はすべて、動作によって解決されます。 さらに、いくつかのチップがあります-テーブルのカスタム数の結合を作成して、先祖/子孫の要求を減らして速度を上げることができます。

githubで見る



実体化されたパス



各要素にフルパスを格納する手法(ファイルシステムのパスと同様)。

+すべての先祖と子孫の迅速な受け取り

+新しい要素をすばやく挿入

-パスの最適でないストレージ、その長さの制限

-一般に、直接の子を取得するには追加の深度フィールドが必要です(特定のベースの実装では、この要件は不要です)

-要素のパスを変更する場合、すべての子孫を更新する必要があります



統合されたAPIの必要性といくつかのメソッドの不在以外に、なぜmatperez / yii2-materialized-pathが私に合わなかったのですか? まず第一に、パスを変更するときに子を更新することはphp-recursedであり、データベースに対して大量のクエリを生成するという事実は非常に悪いことです。 私が実装した動作では、これは1つの要求で行われますが、データベースとの互換性をいくらか犠牲にする必要がありました-CONCAT CONCAT()



およびLENGTH()



関数のサポートが必要です(ほとんどのデータベースではmysql、pqsql、mssqlです)。 別の利点-私の行動では、パスを構築する2つのモードがあります-主キーまたは別のフィールドによるもので、必ずしも一意ではありません。

githubで見る



入れ子セット



+祖先、子孫、隣接ノード、空ノードのクイック選択

+ノード内の子の数を即座に取得

+非再帰的なツリー構築

-特に大きなベースの開始時のツリーの変更が非常に遅い(1つの要素の挿入は、大きなベースで30秒以上続くことがあります)



Yii2には既にCreocoder からの素晴らしい拡張機能がありますが、以下で説明する単一のAPIをサポートするように書き直す必要がありました。 このAPIにはいくつかの利点があります。

githubで見る



入れ子の間隔



+先祖、子孫のクイック選択

+非再帰的なツリー構築

+パラメーターの最適化による高速挿入

-ノードの動きが遅い



このアルゴリズムはあまり一般的ではありませんが、正しいパラメーターが選択されていれば非常に高速です。 この記事でネストされた間隔について詳しく読むことができます。

githubで見る



単一のAPI



上記のすべての動作には共通のAPIがあるため、コードを書き換えずに置き換えることができます。

APIには1つの大きな利点があります-標準のYii2リレーションメカニズムを介した関連オブジェクトへのデュアルアクセス:

 $parent = $model->getParent()->orderBy(['title' => SORT_DESC])->one(); //    ,  ActiveQuery $title1 = $model->parent->title; //   ,    $title2 = $model->parent->title . ' (2)'; //        
      
      





また、特殊性もあります-メソッドmakeRoot(), prependTo(), appendTo(), insertBefore(), insertAfter()



はアクション自体を生成せず、そのタイプを示すだけなので、それらをsave()



する必要がありますsave()





 $node = new Node; $node->title = 'root'; $node->makeRoot()->save();
      
      





これは、 save($runValidation = true, $attributeNames = null)



パラメーターsave($runValidation = true, $attributeNames = null)



をドラッグしないようにするsave($runValidation = true, $attributeNames = null)



です。



複数の動作を同時に使用する特性



Yii2の動作は、それが存在する最初の動作メソッドが実行されるように実装されます。 複数のビヘイビアを共有するには、接続された各ビヘイビアと、データベースから選択するメソッドのツリー修正メソッドを最速で呼び出す必要があります。 これを行うために、これらの機能を実行するTraitが作成されました。 同時に、PhpDocを毎回指定する必要がなくなります。

githubで見る



例:

 use paulzi\adjacencylist\AdjacencyListBehavior; use paulzi\nestedsets\NestedSetsBehavior; use paulzi\autotree\AutoTreeTrait; class Node extends \yii\db\ActiveRecord { use AutoTreeTrait; public function behaviors() { return [ ['class' => AdjacencyListBehavior::className()], ['class' => NestedSetsBehavior::className()], ]; } }
      
      





今:

 $node->parents; //   nested sets $node->parent; //   adjacency list $node->children; //   adjacency list $node->descendants; //   nested sets $node->insertAfter($node2)->save() //     
      
      





最も効果的な組み合わせ:

隣接リスト+マテリアライズドパス

隣接リスト+ネストされたセット

隣接リスト+ネストされた間隔



性能比較



表から、各動作の長所と短所が明確であると思います。

性能表
                                                 DBクエリ実行メモリ時間

テスト1. 12人の子供のレベル3を満たす
    隣接リスト5811 1,567ミリ秒9,591ミリ秒71.3 MB
    ネストされたセット7697 6.672 ms 25.019 ms 105.9 MB
    ネストされた間隔の量= 24 5813 1.765ミリ秒10.442ミリ秒78.7 MB
    ネストされた間隔の量= 12 noPrepend noIns。  5813 1,750ミリ秒10,223ミリ秒78.7 MB
    マテリアライズドパス(ID pr。キーモード)7696 3.169 ms 13.726 ms 92.5 MB
    マテリアライズドパス(属性モード)5811 1,690 ms 9,504 ms 71.6 MB

テスト2. 3人の子供のレベル6を満たす
    隣接リスト3642 982 ms 5.812 ms 44.5 MB
    ネストされたセット4736 5,447 ms 17,859 ms 65.0 MB
    ネストされた間隔の量= 10 3644 1.275 ms 5.976 ms 48.9 MB
    ネストされた間隔の量= 3 noPrepend noIns。  3644 1,271ミリ秒5,993ミリ秒48.9 MB
    実体化されたパス(キーモードのアイデンティティ)4735 1,316 ms 6,920 ms 57.3 MB
    マテリアライズドパス(属性モード)3642 1,129 ms 5,507 ms 44.6 MB

テスト3.先頭に挿入<4%(19657ノットで20)
    隣接リスト100 36 ms 190 ms 4.6 MB
     PaulZi 100 15.768ミリ秒16.712ミリ秒4.7 MB
    ネストされた間隔82 21 ms 150 ms 4.7 MB
    マテリアライズドパス(キーモードのアイデンティティ)120 98 ms 355 ms 4.8 MB
    マテリアライズドパス(属性モード)100 74 ms 334 ms 4.6 MB

テスト4.中央に挿入> 46%<50%(19657ノットで20)
    隣接リスト100 24 ms 150 ms 4.6 MB
    ネストされたセット100 8.212 ms 8.799 ms 4.7 MB
    ネストされた間隔82269 ms 593 ms 4.7 MB
    マテリアライズドパス(IDまたはキーモード)120 35ミリ秒196ミリ秒4.9 MB
    マテリアライズドパス(属性モード)100 287 ms 494 ms 4.6 MB

テスト5.最後に挿入> 96%(19657ノットで20)
    隣接リスト100 31 ms 214 ms 4.5 MB
    ネストされたセット100 487 ms 899 ms 4.7 MB
    ネストされた間隔83 46 ms 187 ms 4.7 MB
    マテリアライズドパス(IDまたはキーモード)120 34ミリ秒229ミリ秒4.8 MB
    マテリアライズドパス(属性モード)100 470 ms 718 ms 4.6 MB

テスト6.最初からの削除<4%(19657ノード中20ノード)
    隣接リストparentJoin = 0 childrenJoin = 0 60 169 ms 257 ms 3.8 MB
    隣接リストparentJoin = 3 childrenJoin = 3 60 87 ms 162 ms 3.8 MB
    ネストされたセット100 16,480 ms 16,902 ms 4.7 MB
    ネストされた間隔60164 ms 250 ms 4.2 MB
    実体化されたパス(キーモードのアイデンティティ)60 87 ms 201 ms 4.0 MB
    マテリアライズドパス(属性モード)60122ミリ秒219ミリ秒4.0 MB

テスト7. appendTo()をランダムノードに(5レベル、1000ノード)
    隣接リスト4001 1,062 ms 5,976 ms 46.1 MB
    ネストされたセット5003 5,428 ms 17,334 ms 64.8 MB
    ネストされた間隔の量= 10 8497 23,301ミリ秒41,060ミリ秒120.7 MB
    ネストされた間隔x64量= 10 7092 11.330ミリ秒23.618ミリ秒97.5 MB
    ネストされた間隔の量= 200.25 noPrep noIns 4009 1.431 ms 6.490 ms 50.2 MB
    ネストされた間隔x64 a = 250.30 noPrep noIns 4003 1.421 ms 6.615 ms 50.0 MB
    マテリアライズドパス(キーモードのアイデンティティ)5003 1,621 ms 8,184 ms 57.8 MB
    マテリアライズドパス(属性モード)4002 1.269 ms 6.169 ms 46.2 MB
    
テスト8.ランダムノードでの任意の操作(5レベル、1000ノード)
    隣接リスト4383 1,330 ms 6,147 ms 53.0 MB
    ネストされたセット5003 9.577 ms 24.334 ms 64.8 MB
    ネストされた間隔の量= 10 7733 8.123 ms 24.031 ms 107.2 MB
    ネストされた間隔x64デフォルト量= 10 5663 3,761ミリ秒14,084ミリ秒75.6 MB
    ネストされた間隔の量= 200.25 4175 1.548 ms 7.223 ms 52.8 MB
    ネストされた間隔x64 a = 250.30予約= 2 4003 1.541 ms 6.753 ms 50.0 MB
    マテリアライズドパス(アイデンティティpr。キーモード)5392 3.211 ms 11.771 ms 65.0 MB
    マテリアライズドパス(属性モード)4377 2.902 ms 10.132 ms 53.2 MB
    
テスト9.開始時のノードの移動<4%(19657ノードのうち20ノード)
    隣接リスト112 39 ms 261 ms 4.5 MB
    ネストされたセット140218 ms 566 ms 5.5 MB
    ネストされた間隔160180 ms 573 ms 6.0 MB
    マテリアライズドパス(IDまたはキーモード)128 38 ms 307 ms 4.9 MB
    マテリアライズドパス(属性モード)128 159 ms 495 ms 4.9 MB

テスト10.ノードを最後から最初に移動する<4%> 96%(19657ノードのうち20ノード)
    ネストされたセット140 16,991 ms 17,845 ms 5.5 MB
    ネストされた間隔160 16,972 ms 17,854 ms 6.0 MB
    マテリアライズドパス(キーモードのアイデンティティ)132 49ミリ秒319ミリ秒4.9 MB
    マテリアライズドパス(属性モード)132205ミリ秒502ミリ秒4.9 MB
    隣接リスト112 33 ms 217 ms 4.5 MB
    
テスト11.すべてのノードの選択(19657個)
    隣接リスト1 33 ms 890 ms 179.1 MB
    ネストされたセット1 40 ms 1,208 ms 215.2 MB
    ネストされた間隔1 42 ms 1,247 ms 225.3 MB
    実体化されたパス(キーモードのアイデンティティ)1 46 ms 1,240 ms 209.0 MB
    マテリアライズドパス(属性モード)1 44 ms 1.106 ms 209.0 MB
    
テスト12.子と子孫の選択(19657ノードのツリーの中央にある819ノードの場合)
    隣接リストparentJoin = 0 childrenJoin = 0 2562 720 ms 1.969 ms 36.9 MB
    隣接リストparentJoin = 3 childrenJoin = 3 2461 704 ms 1.966 ms 35.3 MB
    ネストされたセット1641 522 ms 1.585 ms 25.0 MB
    ネストされた間隔1641 579 ms 1.657 ms 25.0 MB
    マテリアライズドパス(ID pr。キーモード)1641 569 ms 1,626 ms 23.4 MB
    マテリアライズドパス(属性モード)1641 793ミリ秒6.552ミリ秒44.7 MB

テスト13.親の選択(19657ノードのツリーの中央にある819ノードの場合)
    隣接リストparentJoin = 0 childrenJoin = 0 3180 948 ms 2.304 ms 51.2 MB
    隣接リストparentJoin = 3 childrenJoin = 3 1641 486 ms 1.495 ms 30.8 MB
    ネストされたセット821 3,238 ms 3,979 ms 20.7 MB
    ネストされた間隔821 3.292 ms 4.147 ms 22.0 MB
    マテリアライズドパス(ID pr。キーモード)821 292 ms 902 ms 21.2 MB
    マテリアライズドパス(属性モード)821 582ミリ秒4,574ミリ秒24.7 MB

テスト14.隣接ノードの選択(19657ノードのツリーの中央にある819ノードの場合)
    隣接リストparentJoin = 0 childrenJoin = 0 1641 535 ms 1,442 ms 23.7 MB
    隣接リストparentJoin = 3 childrenJoin = 3 1641 508 ms 1,421 ms 23.6 MB
    ネストされたセット1641 513ミリ秒1.428ミリ秒24.5 MB
    ネストされた間隔1641 19.681ミリ秒21.326ミリ秒27.5 MB
    マテリアライズドパス(ID pr。キーモード)1641 730ミリ秒1,695ミリ秒24.3 MB
    マテリアライズドパス(属性モード)1641 1.892 ms 2.964 ms 24.3 MB

テスト15.空のノードの選択(19657ノードのツリーの中央にある819ノードの場合)
    隣接リストparentJoin = 0 childrenJoin = 0 1833 568 ms 1,743 ms 32.6 MB
    隣接リストparentJoin = 3 childrenJoin = 3 1732 556 ms 1.891 ms 31.3 MB
    ネストされたセット821305 ms 908 ms 18.4 MB
    ネストされた間隔821 10,450 ms 11,166 ms 18.8 MB
    マテリアライズドパス(ID pr。キーモード)821 7.968 ms 8.434 ms 18.5 MB
    マテリアライズドパス(属性モード)821 14.349ミリ秒19.105ミリ秒21.3 MB


GitHubリンク



隣接リスト

実体化されたパス

入れ子セット

入れ子の間隔

自動ツリー特性



All Articles