EC-CUBEは、MySQLの場合Viewを使用せず毎回テーブルをJOINしています。
View非対応の古いバージョンのMySQLのためにそのような作りになっているようですが、そのせいで恐ろしい弊害が発生します。
EC-CubeをMySQLで実装すると、遅すぎて使い物にならなくなります。
どのくらい酷いかって中野区立図書館を遙かに超えるレベル。
商品数わずか1000件程度でストレスを感じるほどの遅延が出るようになり、10000件登録しようものならTimeoutをいじらないといけないというどうしようもない出来映えです。
どうしてこんなことになってるのか/class/db/dbfactory/SC_DB_DBFactory_MYSQL.phpを見てみましょう。
SC_DB_DBFactory_MYSQL::viewToSubQuery()とかな。
なんだよこれ。
読んでみると、サブクエリで全件取得して、その後でWHERE句で絞り込みを行うというとんでもなく非効率な書き方のSQLとなっています。
なんかもうちょっとどうにかならんのか、と見ているとサブクエリ中に'&&noncls_where&&'という謎の文字列を発見しました。
具体的なSQLは長大すぎて引用できませんが、構造的には次のような作りです。
'SELECT * FROM ( SELECT * FROM dtb_products &&noncls_where&& ) AS T1 WHERE (検索)'
ビューを参照するSQLを実行すると、
SC_Query::select()
SC_DB_DBFactory::getInstance()
SC_DB_DBFactory_MYSQL::sfChangeMySQL()
SC_DB_DBFactory_MYSQL::sfChangeView()
SC_DB_DBFactory_MYSQL::viewToSubQuery()
SC_DB_DBFactory_MYSQL::getWhereConverter()
という順番でSQLが変換されます。
SC_DB_DBFactory_MYSQL::viewToSubQuery()でビューに相当するSQLに書き換えられ、SC_DB_DBFactory_MYSQL::getWhereConverter()で'&&noncls_where&&'のような文字列を置換して返します。
つまり、'&&noncls_where&&'に抽出範囲を狭める条件文をねじ込めば、SQLの実行速度が速くなりそうです。
サブクエリの返り値が10000件と1件では圧倒的な差が生まれることは間違いありません。
で、具体的なSC_DB_DBFactory_MYSQL::getWhereConverter()はどうなってるかというと、
実装されていません。
さらに、コントローラからSC_DB_DBFactoryを直接触る術がないため、外部から値を注入することもできません。
selectするたびに毎回SC_DB_DBFactoryインスタンスを作成してるんだよねこれ。
どうにかするためにはSC_Queryを直接書き換える必要があります。
/eccube/data/class/db/dbfactory/SC_DB_DBFactory_MYSQL.php
/eccube/data/class/SC_Query.php
以上で形ができました。
あとはSELECTを実行する際に、
などとすれば動作します。
上記はあくまで最低限の形なので、NOTICEが出たりコントローラに直接mysql_real_escape_string()があったりする残念な作りです。
SC_Query::escape()でPear_DB::escapeSimple()あたりを拾ってくるようにしたりした方がよいでしょう。
あと'&&noncls_where&&'以外の置換文字列はそもそも存在すらしていないので、追加するなり削除するなりしてしまいましょう。
ちなみにPostgresの場合は相当すっきりしたSQLになっていますが、これは単にCREATE VIEWに面倒ごとを突っ込んでいるだけです。
つうかMySQLにviewが実装されたのは2005年なんだからview非対応への対応とかもういいよ。
View非対応の古いバージョンのMySQLのためにそのような作りになっているようですが、そのせいで恐ろしい弊害が発生します。
EC-CubeをMySQLで実装すると、遅すぎて使い物にならなくなります。
どのくらい酷いかって中野区立図書館を遙かに超えるレベル。
商品数わずか1000件程度でストレスを感じるほどの遅延が出るようになり、10000件登録しようものならTimeoutをいじらないといけないというどうしようもない出来映えです。
どうしてこんなことになってるのか/class/db/dbfactory/SC_DB_DBFactory_MYSQL.phpを見てみましょう。
SC_DB_DBFactory_MYSQL::viewToSubQuery()とかな。
なんだよこれ。
読んでみると、サブクエリで全件取得して、その後でWHERE句で絞り込みを行うというとんでもなく非効率な書き方のSQLとなっています。
なんかもうちょっとどうにかならんのか、と見ているとサブクエリ中に'&&noncls_where&&'という謎の文字列を発見しました。
具体的なSQLは長大すぎて引用できませんが、構造的には次のような作りです。
'SELECT * FROM ( SELECT * FROM dtb_products &&noncls_where&& ) AS T1 WHERE (検索)'
ビューを参照するSQLを実行すると、
SC_Query::select()
SC_DB_DBFactory::getInstance()
SC_DB_DBFactory_MYSQL::sfChangeMySQL()
SC_DB_DBFactory_MYSQL::sfChangeView()
SC_DB_DBFactory_MYSQL::viewToSubQuery()
SC_DB_DBFactory_MYSQL::getWhereConverter()
という順番でSQLが変換されます。
SC_DB_DBFactory_MYSQL::viewToSubQuery()でビューに相当するSQLに書き換えられ、SC_DB_DBFactory_MYSQL::getWhereConverter()で'&&noncls_where&&'のような文字列を置換して返します。
つまり、'&&noncls_where&&'に抽出範囲を狭める条件文をねじ込めば、SQLの実行速度が速くなりそうです。
サブクエリの返り値が10000件と1件では圧倒的な差が生まれることは間違いありません。
で、具体的なSC_DB_DBFactory_MYSQL::getWhereConverter()はどうなってるかというと、
function getWhereConverter() { return array( "&&crscls_where&&" => "", "&&crsprdcls_where&&" =>"", "&&noncls_where&&" => "", "&&allcls_where&&" => "", "&&allclsdtl_where&&" => "", "&&prdcls_where&&" => "", "&&catcnt_where&&" => "" ); }
実装されていません。
さらに、コントローラからSC_DB_DBFactoryを直接触る術がないため、外部から値を注入することもできません。
selectするたびに毎回SC_DB_DBFactoryインスタンスを作成してるんだよねこれ。
どうにかするためにはSC_Queryを直接書き換える必要があります。
/eccube/data/class/db/dbfactory/SC_DB_DBFactory_MYSQL.php
function getWhereConverter() { return array( "&&crscls_where&&" => $this->where['crscls_where'], "&&crsprdcls_where&&" => $this->where['crsprdcls_where'], "&&noncls_where&&" => $this->where['noncls_where'], "&&allcls_where&&" => $this->where['allcls_where'], "&&allclsdtl_where&&" => $this->where['allclsdtl_where'], "&&prdcls_where&&" => $this->where['prdcls_where'], "&&catcnt_where&&" => $this->where['catcnt_where'] ); }
/eccube/data/class/SC_Query.php
function select($col, $table, $where = "", $arrval = array()){ $sqlse = $this->getsql($col, $table, $where); $dbFactory = SC_DB_DBFactory_Ex::getInstance(); $dbFactory->where = $this->where; $sqlse = $dbFactory->sfChangeMySQL($sqlse); $ret = $this->conn->getAll($sqlse, $arrval); return $ret; }
以上で形ができました。
あとはSELECTを実行する際に、
$objQuery->where['noncls_where'] = ' WHERE product_id = '.mysql_real_escape_string($product_id); $arrRet = $objQuery->select("*", "vw_products_nonclass AS alldtl", "product_id = ?", array($product_id));
などとすれば動作します。
上記はあくまで最低限の形なので、NOTICEが出たりコントローラに直接mysql_real_escape_string()があったりする残念な作りです。
SC_Query::escape()でPear_DB::escapeSimple()あたりを拾ってくるようにしたりした方がよいでしょう。
あと'&&noncls_where&&'以外の置換文字列はそもそも存在すらしていないので、追加するなり削除するなりしてしまいましょう。
ちなみにPostgresの場合は相当すっきりしたSQLになっていますが、これは単にCREATE VIEWに面倒ごとを突っ込んでいるだけです。
つうかMySQLにviewが実装されたのは2005年なんだからview非対応への対応とかもういいよ。
PR
トラックバック
トラックバックURL: