db icon indicating copy to clipboard operation
db copied to clipboard

Add Hint Index in queries

Open fdezmc opened this issue 8 years ago • 37 comments

I need force index in my project, so I've add the method addHintIndex to set the hints indexes associated to a table. The param is an assoc array of the hintIndex(s) to be used on the query. The key is the name of the table to be hinted. The value is the hint settings or an array of hints settings to apply. Each hint setting is compose by one to three string values defining the hint and one array defining the index(s) to hint.

Here are some examples:

$query = (new \yii\db\Query())->from(['t' => 'user'])->addHintIndex(['user' => [
    ['force', 'index', ['primary']],
    ['ignore', 'index', 'order by', ['i1']],
]])->leftJoin('profile as p', 'user.id = profile.user_id')->addHintIndex(['profile' => [
    'use', 'index', ['i2']
]]);

// sql -> SELECT * FROM `user` `t` FORCE INDEX (primary) IGNORE INDEX FOR ORDER BY (i1)
 LEFT JOIN `profile` `p` ON user.id = profile.user_id USE INDEX (i2)
$users = User::find()->addHintIndex(['user' => [['use', 'index', ['primary']]]])->one();

// sql -> SELECT * FROM {{%user}} USE INDEX (primary)

I have no experience enought with other drivers than mysql, so just write functionality for mysql and sqlite drivers (the former tested the last pending for testing).

Driver overwritting can be done on:

  • /yii2/framework/db/mysql/QueryBuilder.php
  • /yii2/framework/db/sqlite/QueryBuilder.php (require testing)*
  • /yii2/framework/db/cubrid/QueryBuilder.php (trace a message and ignore addHintIndex)*
  • /yii2/framework/db/mssql/QueryBuilder.php (trace a message and ignore addHintIndex)*
  • /yii2/framework/db/oci/QueryBuilder.php (trace a message and ignore addHintIndex)*
  • /yii2/framework/db/pgsql/QueryBuilder.php (trace a message and ignore addHintIndex)*

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

fdezmc avatar Feb 16 '16 14:02 fdezmc

As far as I see this is already possible with existing methods. no need to add anything:

// your first example:
$query = (new Query)->from([new Expression('{{%user}} USE INDEX (primary)')]);
// your second example
$query = (new Query)
       ->from([new Expression('`user` `t` FORCE INDEX (primary) IGNORE INDEX FOR ORDER BY (i1)')])
       ->leftJoin(['p' => 'profile'], 'user.id = profile.user_id USE INDEX (i2)');

cebe avatar Feb 16 '16 20:02 cebe

Added a test to verify this works.

cebe avatar Feb 16 '16 20:02 cebe

I needed hint index with activeRecord. Thought it was not possible to set an expression on from clause with ActiveRecord, now I found the way:

$user = User::find()->from([new yii\db\Expression('{{%user}} USE INDEX (primary)')]);

but the use of Expression this way, will break the abstraction layer requiring recode if you decide to change to other db driver. Adding hintIndex method could solve this issue without loosing funtionality.

fdezmc avatar Feb 16 '16 21:02 fdezmc

@fdezmc different DB engines are working differently with indexes so what you're forcing in MySQL to gain extra performance may be absolute performance hog when using PostgreSQL. If you're down to this level of working with DB you're already not likely to switch easily.

samdark avatar Feb 16 '16 22:02 samdark

Absolutely, thank you very much for your comments.

fdezmc avatar Feb 16 '16 22:02 fdezmc

Hi @samdark , as a former DBA, query optimisation is avery important part for large application development. The Index Hinting is available in almost all database engines, is not only present in MySQL.

As a developer, you can specify the indexes you prefer in that query. The index hint can be generate to Oracle, PG, Mysql, etc... so the code will still works.

But, by the way. How many apps are developed for easy DB Engine Switch???

If you are concerned about easy switching between database, there is a lot of queries in MySQL that doesn't works in PG. Or at least, doesn't work as expected. It's imposible to make a compatible ORM without losing a lot of performance.

Adding Index Hinting, you are not breaking nothing, you are giving an option available in all databases.

juanmacias avatar Jul 07 '16 09:07 juanmacias

But, by the way. How many apps are developed for easy DB Engine Switch???

Some. Installable products mainly: CMSes, CRMs etc.

samdark avatar Jul 07 '16 09:07 samdark

I've migrated some apps from MySQL to PG in yii2 and there is a lot of work to do. For example with nulls and 0.

On Thu, Jul 7, 2016 at 11:11 AM, Alexander Makarov <[email protected]

wrote:

But, by the way. How many apps are developed for easy DB Engine Switch??? Some. Installable products mainly: CMSes, CRMs etc.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/yiisoft/yii2/issues/10869#issuecomment-231024872, or mute the thread https://github.com/notifications/unsubscribe/AEk1ZX1OIdrdggZ1PeqydqFSx8ZkkMgvks5qTMLBgaJpZM4HbM1b .

Juan Macias CEO QaShops.com 659203561

juanmacias avatar Jul 07 '16 09:07 juanmacias

Absolutely.

samdark avatar Jul 07 '16 09:07 samdark

So, you won't approve a feature to help developers optimice the SQLs on major database, just because you want Yii2 to be compatible with all databases.

A decision that helps 1% of developers and f*cks 99% developers of large applications.

juanmacias avatar Jul 12 '16 19:07 juanmacias

Don't mind, we have managed to extend QueryBuilder to add this feature and being compatible with Yii2.

juanmacias avatar Jul 12 '16 19:07 juanmacias

Hey @juanmacias , mind sharing the extension?

Cheers

marekpetras avatar Sep 15 '16 23:09 marekpetras

Yii2 is known for its speed. Therefore this functionality should be adopted.

dynasource avatar Nov 11 '16 12:11 dynasource

the examples above with Expression did not seem to work. Thats confusing. However, I managed to get it working for ->from with an example like this:

        $this->from(['USE INDEX (visitor_timestamp)' => $tableName]);

Apart from this workaround, we should have this functionality in core. I don't see why this is left out. Even if 1 or 2 DB engines don't support it, should not mean it shouldnt be implemented.

People have spent many, many hours to have their architecture ready to actually benefit from these indexes. If it then seems to be useless, this is frustrating and even more worse, the difference of a page loading in 10ms or 10 seconds.

dynasource avatar Nov 11 '16 13:11 dynasource

the examples above with Expression did not seem to work.

@dynasource what exactly does not work for you? There is a test that checks that it works: b9880f32474bf30bd7662ee7f3465a0e58a2be2b

cebe avatar Nov 13 '16 21:11 cebe

this example did not work:

$query = (new Query)->from([new Expression('{{%user}} USE INDEX (primary)')]);

I have to recheck for the specific error

dynasource avatar Nov 13 '16 21:11 dynasource

I think I found the issue again: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'USE INDEX (visitor_timestamp)' at line 1 The SQL being executed was: SHOW FULL COLUMNS FROM stats_normal_2016 USE INDEX (visitor_timestamp)

dynasource avatar Nov 15 '16 12:11 dynasource

There must be something more specific you do for that error. The line you posted works fine: 6caedd1

cebe avatar Nov 15 '16 13:11 cebe

As mentioned in #11807 this don´t work with joinWith.

// works!!!
$query = (new Query)->from([new Expression('{{%user}} USE INDEX (primary)')]);

// joinWith relation fails
$query = (new Query)->from([new Expression('{{%user}} USE INDEX (primary)')])
->joinWith('userProfile');

Note: userProfile is a relation between user and profile ON user.id = profile.user_id

ActiveQuery class method joinWithRelation takes as $parentAlias an expression resulting in:

SELECT * FROM user USE INDEX (primary) LEFT JOIN profile ON user USE INDEX (primary).id = profile.user_id

instead of

SELECT * FROM user USE INDEX (primary) LEFT JOIN profile ON user.id = profile.user_id

fdezmc avatar Nov 22 '16 12:11 fdezmc

Well, I didn't want to share the code because is an horrible workaround (but works), I'm not proud of using it, but is the only way I've found.

How to use install it:

  1. Add the code to your project (I had no time to release it as a module, any volunteer? )

  2. Add in your main_local:

return [
    'components' => [
        'db' => [
            'class' => 'common\components\qamysqlconnection\Connection',

  1. In the Active Models you want to enable ForceIndex, just put this:
public static function find()
   {
    return new QaActiveQuery(get_called_class());
}

or override your Query class.

How to use it With this, you will have a new fromOption() method in the ActiveQuery where you can specify whatever you want, like a "force Index" Example: Product::find()->fromOption("FORCE INDEX(supplier_reference, parent_id)")->all();

qamysqlconnection.zip

juanmacias avatar Nov 22 '16 13:11 juanmacias

Thanks Juan for your solution.

bhagwatchouhan avatar Feb 14 '17 04:02 bhagwatchouhan

Please, resolve this problem. It hurts. And as plans for 2.1.1 are dropped, this issue became dropped too

malsatin avatar Jun 20 '18 01:06 malsatin

Plans aren't dropped. Versioning was adjusted, that's it.

samdark avatar Jun 21 '18 11:06 samdark

please resolve this problem.I found that through Active Record I have no way to use force index in mysql.

ailiangkuai avatar Aug 02 '18 11:08 ailiangkuai

I have prepared slight improvement over @juanmacias code Put this folder into ./common/extensions/qaConnection

Then you can just add use QaRecordTrait; to your ActiveRecord model and use it like that

YourModel::find()
   ->tableOption('FORCE INDEX(`' . $index . '`)')
   ->...

or

YourModel::find()
   ->from(['your_model' => 'model'])
   ->tableOption(['model' => 'FORCE INDEX(`' . $index . '`)'])
   ->...

Code: qaConnection.zip

malsatin avatar Aug 02 '18 12:08 malsatin

Thank you for your help! @MattRh

ailiangkuai avatar Aug 03 '18 06:08 ailiangkuai

https://github.com/yiisoft/yii2/pull/13607

samdark avatar Apr 24 '19 11:04 samdark

Is there any way to do this? When I use the from option it overwrites all the joins as discussed above. Seems like this should be in to be honest. Can I help?

julianrutten avatar Sep 30 '19 17:09 julianrutten

Not in Yii 2.

samdark avatar Oct 01 '19 08:10 samdark

Shouldnt this be closed then if you have given up?

julianrutten avatar Oct 01 '19 11:10 julianrutten