Skip to content

Commit 0c69ba5

Browse files
authored
Merge pull request #1247 from magento-performance/MAGETWO-69130
[performance] MAGETWO-69130: Non effective query for catalog search & layered navigation
2 parents 8868a95 + bbce379 commit 0c69ba5

File tree

48 files changed

+4040
-957
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4040
-957
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Bundle\Model\ResourceModel\Indexer;
8+
9+
use Magento\Catalog\Api\Data\ProductInterface;
10+
use Magento\Framework\DB\Select;
11+
12+
/**
13+
* Class BundleOptionStockDataSelectBuilder
14+
* Is used to create Select object that is used for Bundle product stock status indexation
15+
*
16+
* @see \Magento\Bundle\Model\ResourceModel\Indexer\Stock::_prepareBundleOptionStockData
17+
*/
18+
class BundleOptionStockDataSelectBuilder
19+
{
20+
/**
21+
* @param \Magento\Framework\App\ResourceConnection $resourceConnection
22+
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
23+
*/
24+
public function __construct(
25+
\Magento\Framework\App\ResourceConnection $resourceConnection,
26+
\Magento\Framework\EntityManager\MetadataPool $metadataPool
27+
) {
28+
$this->resourceConnection = $resourceConnection;
29+
$this->metadataPool = $metadataPool;
30+
}
31+
32+
/**
33+
* @param string $idxTable
34+
* @return Select
35+
*/
36+
public function buildSelect($idxTable)
37+
{
38+
$select = $this->resourceConnection->getConnection()->select();
39+
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
40+
41+
$select->from(
42+
['product' => $this->resourceConnection->getTableName('catalog_product_entity')],
43+
['entity_id']
44+
)->join(
45+
['bo' => $this->resourceConnection->getTableName('catalog_product_bundle_option')],
46+
"bo.parent_id = product.$linkField",
47+
[]
48+
)->join(
49+
['cis' => $this->resourceConnection->getTableName('cataloginventory_stock')],
50+
'',
51+
['website_id', 'stock_id']
52+
)->joinLeft(
53+
['bs' => $this->resourceConnection->getTableName('catalog_product_bundle_selection')],
54+
'bs.option_id = bo.option_id',
55+
[]
56+
)->joinLeft(
57+
['i' => $idxTable],
58+
'i.product_id = bs.product_id AND i.website_id = cis.website_id AND i.stock_id = cis.stock_id',
59+
[]
60+
)->joinLeft(
61+
['e' => $this->resourceConnection->getTableName('catalog_product_entity')],
62+
'e.entity_id = bs.product_id',
63+
[]
64+
)->group(
65+
['product.entity_id', 'cis.website_id', 'cis.stock_id', 'bo.option_id']
66+
)->columns(['option_id' => 'bo.option_id']);
67+
68+
return $select;
69+
}
70+
}

app/code/Magento/Bundle/Model/ResourceModel/Indexer/Stock.php

Lines changed: 39 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66
namespace Magento\Bundle\Model\ResourceModel\Indexer;
77

8-
use Magento\Catalog\Api\Data\ProductInterface;
98
use Magento\CatalogInventory\Model\Indexer\Stock\Action\Full;
109
use Magento\Framework\App\ObjectManager;
1110

@@ -22,26 +21,47 @@ class Stock extends \Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\
2221
private $activeTableSwitcher;
2322

2423
/**
25-
* Stock constructor.
24+
* @var \Magento\Bundle\Model\ResourceModel\Indexer\StockStatusSelectBuilder
25+
*/
26+
private $stockStatusSelectBuilder;
27+
28+
/**
29+
* @var \Magento\Bundle\Model\ResourceModel\Indexer\BundleOptionStockDataSelectBuilder
30+
*/
31+
private $bundleOptionStockDataSelectBuilder;
32+
33+
/**
34+
* Class constructor
35+
*
2636
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
2737
* @param \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy
2838
* @param \Magento\Eav\Model\Config $eavConfig
2939
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
3040
* @param null $connectionName
3141
* @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher
42+
* @param StockStatusSelectBuilder|null $stockStatusSelectBuilder
43+
* @param BundleOptionStockDataSelectBuilder|null $bundleOptionStockDataSelectBuilder
3244
*/
3345
public function __construct(
3446
\Magento\Framework\Model\ResourceModel\Db\Context $context,
3547
\Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy,
3648
\Magento\Eav\Model\Config $eavConfig,
3749
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
3850
$connectionName = null,
39-
\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null
51+
\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null,
52+
StockStatusSelectBuilder $stockStatusSelectBuilder = null,
53+
BundleOptionStockDataSelectBuilder $bundleOptionStockDataSelectBuilder = null
4054
) {
4155
parent::__construct($context, $tableStrategy, $eavConfig, $scopeConfig, $connectionName);
42-
$this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get(
43-
\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class
44-
);
56+
57+
$this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()
58+
->get(\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class);
59+
60+
$this->stockStatusSelectBuilder = $stockStatusSelectBuilder ?: ObjectManager::getInstance()
61+
->get(StockStatusSelectBuilder::class);
62+
63+
$this->bundleOptionStockDataSelectBuilder = $bundleOptionStockDataSelectBuilder ?: ObjectManager::getInstance()
64+
->get(BundleOptionStockDataSelectBuilder::class);
4565
}
4666

4767
/**
@@ -64,46 +84,21 @@ protected function _getBundleOptionTable()
6484
protected function _prepareBundleOptionStockData($entityIds = null, $usePrimaryTable = false)
6585
{
6686
$this->_cleanBundleOptionStockData();
67-
$linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
87+
$connection = $this->getConnection();
6888
$table = $this->getActionType() === Full::ACTION_TYPE
6989
? $this->activeTableSwitcher->getAdditionalTableName($this->getMainTable())
7090
: $this->getMainTable();
7191
$idxTable = $usePrimaryTable ? $table : $this->getIdxTable();
72-
$connection = $this->getConnection();
73-
$select = $connection->select()->from(
74-
['product' => $this->getTable('catalog_product_entity')],
75-
['entity_id']
76-
);
77-
$select->join(
78-
['bo' => $this->getTable('catalog_product_bundle_option')],
79-
"bo.parent_id = product.$linkField",
80-
[]
81-
);
92+
$select = $this->bundleOptionStockDataSelectBuilder->buildSelect($idxTable);
93+
8294
$status = new \Zend_Db_Expr(
83-
'MAX(' . $connection->getCheckSql('e.required_options = 0', 'i.stock_status', '0') . ')'
84-
);
85-
$select->join(
86-
['cis' => $this->getTable('cataloginventory_stock')],
87-
'',
88-
['website_id', 'stock_id']
89-
)->joinLeft(
90-
['bs' => $this->getTable('catalog_product_bundle_selection')],
91-
'bs.option_id = bo.option_id',
92-
[]
93-
)->joinLeft(
94-
['i' => $idxTable],
95-
'i.product_id = bs.product_id AND i.website_id = cis.website_id AND i.stock_id = cis.stock_id',
96-
[]
97-
)->joinLeft(
98-
['e' => $this->getTable('catalog_product_entity')],
99-
'e.entity_id = bs.product_id',
100-
[]
101-
)->group(
102-
['product.entity_id', 'cis.website_id', 'cis.stock_id', 'bo.option_id']
103-
)->columns(
104-
['option_id' => 'bo.option_id', 'status' => $status]
95+
'MAX('
96+
. $connection->getCheckSql('e.required_options = 0', 'i.stock_status', '0')
97+
. ')'
10598
);
10699

100+
$select->columns(['status' => $status]);
101+
107102
if ($entityIds !== null) {
108103
$select->where('product.entity_id IN(?)', $entityIds);
109104
}
@@ -133,27 +128,18 @@ protected function _getStockStatusSelect($entityIds = null, $usePrimaryTable = f
133128
{
134129
$this->_prepareBundleOptionStockData($entityIds, $usePrimaryTable);
135130
$connection = $this->getConnection();
131+
136132
$select = parent::_getStockStatusSelect($entityIds, $usePrimaryTable);
137-
$select->reset(
138-
\Magento\Framework\DB\Select::COLUMNS
139-
)->columns(
140-
['e.entity_id', 'cis.website_id', 'cis.stock_id']
141-
)->joinLeft(
142-
['o' => $this->_getBundleOptionTable()],
143-
'o.entity_id = e.entity_id AND o.website_id = cis.website_id AND o.stock_id = cis.stock_id',
144-
[]
145-
)->columns(
146-
['qty' => new \Zend_Db_Expr('0')]
147-
);
133+
$select = $this->stockStatusSelectBuilder->buildSelect($select);
148134

135+
$statusNotNullExpr = $connection->getCheckSql('o.stock_status IS NOT NULL', 'o.stock_status', '0');
149136
$statusExpr = $this->getStatusExpression($connection);
137+
150138
$select->columns(
151139
[
152140
'status' => $connection->getLeastSql(
153141
[
154-
new \Zend_Db_Expr(
155-
'MIN(' . $connection->getCheckSql('o.stock_status IS NOT NULL', 'o.stock_status', '0') . ')'
156-
),
142+
new \Zend_Db_Expr('MIN(' . $statusNotNullExpr . ')'),
157143
new \Zend_Db_Expr('MIN(' . $statusExpr . ')'),
158144
]
159145
),
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Bundle\Model\ResourceModel\Indexer;
8+
9+
use Magento\Framework\DB\Select;
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
12+
13+
/**
14+
* Class StockStatusSelectBuilder
15+
* Is used to create Select object that is used for Bundle product stock status indexation
16+
*
17+
* @see \Magento\Bundle\Model\ResourceModel\Indexer\Stock::_getStockStatusSelect
18+
*/
19+
class StockStatusSelectBuilder
20+
{
21+
22+
/**
23+
* @param \Magento\Framework\App\ResourceConnection $resourceConnection
24+
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
25+
* @param \Magento\Eav\Model\Config $eavConfig
26+
*/
27+
public function __construct(
28+
\Magento\Framework\App\ResourceConnection $resourceConnection,
29+
\Magento\Framework\EntityManager\MetadataPool $metadataPool,
30+
\Magento\Eav\Model\Config $eavConfig
31+
) {
32+
$this->resourceConnection = $resourceConnection;
33+
$this->metadataPool = $metadataPool;
34+
$this->eavConfig = $eavConfig;
35+
}
36+
37+
/**
38+
* @param Select $select
39+
* @return Select
40+
* @throws \Exception
41+
*/
42+
public function buildSelect(Select $select)
43+
{
44+
$select = clone $select;
45+
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
46+
$linkField = $metadata->getLinkField();
47+
48+
$select->reset(
49+
Select::COLUMNS
50+
)->columns(
51+
['e.entity_id', 'cis.website_id', 'cis.stock_id']
52+
)->joinLeft(
53+
['o' => $this->resourceConnection->getTableName('catalog_product_bundle_stock_index')],
54+
'o.entity_id = e.entity_id AND o.website_id = cis.website_id AND o.stock_id = cis.stock_id',
55+
[]
56+
)->joinInner(
57+
['cpr' => $this->resourceConnection->getTableName('catalog_product_relation')],
58+
'e.' . $linkField . ' = cpr.parent_id',
59+
[]
60+
)->columns(
61+
['qty' => new \Zend_Db_Expr('0')]
62+
);
63+
64+
if ($metadata->getIdentifierField() === $metadata->getLinkField()) {
65+
$select->joinInner(
66+
['cpei' => $this->resourceConnection->getTableName('catalog_product_entity_int')],
67+
'cpr.child_id = cpei.' . $linkField
68+
. ' AND cpei.attribute_id = ' . $this->getAttribute('status')->getId()
69+
. ' AND cpei.value = ' . ProductStatus::STATUS_ENABLED,
70+
[]
71+
);
72+
} else {
73+
$select->joinInner(
74+
['cpel' => $this->resourceConnection->getTableName('catalog_product_entity')],
75+
'cpel.entity_id = cpr.child_id',
76+
[]
77+
)->joinInner(
78+
['cpei' => $this->resourceConnection->getTableName('catalog_product_entity_int')],
79+
'cpel.'. $linkField . ' = cpei.' . $linkField
80+
. ' AND cpei.attribute_id = ' . $this->getAttribute('status')->getId()
81+
. ' AND cpei.value = ' . ProductStatus::STATUS_ENABLED,
82+
[]
83+
);
84+
}
85+
86+
return $select;
87+
}
88+
89+
/**
90+
* Retrieve catalog_product attribute instance by attribute code
91+
*
92+
* @param string $attributeCode
93+
* @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute
94+
*/
95+
private function getAttribute($attributeCode)
96+
{
97+
return $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode);
98+
}
99+
}

app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Magento\Framework\App\ObjectManager;
2121
use Magento\Framework\Exception\LocalizedException;
2222
use Magento\CatalogInventory\Model\Indexer\Stock\AbstractAction;
23+
use Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\StockInterface;
2324

2425
/**
2526
* Class Full reindex action
@@ -117,6 +118,8 @@ public function execute($ids = null)
117118
{
118119
try {
119120
$this->useIdxTable(false);
121+
$this->cleanIndexersTables($this->_getTypeIndexers());
122+
120123
$entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
121124

122125
$columns = array_keys($this->_getConnection()->describeTable($this->_getIdxTable()));
@@ -161,4 +164,27 @@ public function execute($ids = null)
161164
throw new LocalizedException(__($e->getMessage()), $e);
162165
}
163166
}
167+
168+
/**
169+
* Delete all records from index table
170+
* Used to clean table before re-indexation
171+
*
172+
* @param array $indexers
173+
* @return void
174+
*/
175+
private function cleanIndexersTables(array $indexers)
176+
{
177+
$tables = array_map(
178+
function (StockInterface $indexer) {
179+
return $this->activeTableSwitcher->getAdditionalTableName($indexer->getMainTable());
180+
},
181+
$indexers
182+
);
183+
184+
$tables = array_unique($tables);
185+
186+
foreach ($tables as $table) {
187+
$this->_getConnection()->truncateTable($table);
188+
}
189+
}
164190
}

0 commit comments

Comments
 (0)