sequelize: MSSQL: Implicit conversion causes table scan -- Prepending N to strings precludes index use on varchar columns
What are you doing?
Querying a table and filtering on a varchar column. The issue is most easily illustrated using raw query code although it applies to both raw queries and model-generated ones.
create table names (
name varchar(100) not null primary key clustered
)
sequelize.query('select * from names where name=:name', { replacements: { name: 'hello' } });
What do you expect to happen?
Sequelize to construct SQL without prepending N to :name
SQL sent to server:
select * from names where name='hello'
SQL Server performs an index seek using index defined above.
What is actually happening?
N is prepended.
SQL sent to server:
select * from names where name=N'hello'
SQL Server performs a table scan after doing an implicit conversion on N’hello’
Notes
I believe this problem was introduced in response to issue #3752 to handle unicode strings in mssql. The problem is that automatically treating all strings as unicode precludes index use on varchar columns which can significantly hurt performance.
The code in question begins here.
Workaround
A workaround is possible for raw queries but not model-generated ones.
Example:
sequelize.query('select * from names where name=cast(:name as varchar(100)), { replacements: { name: 'hello' } });
Dialect: mssql Database version: All (Tested SQL Server 2012/2016) Sequelize version: 4.34.0 Tested with latest release: Yes
About this issue
- Original URL
- State: open
- Created 6 years ago
- Reactions: 3
- Comments: 20 (2 by maintainers)
@rconstantine
You just need to add that code before you actually perform any queries. You don’t need to modify any other code.
Something like this should work (I think)
You can use the shorter
CONVERT
syntax for the workaround and not need the data length if the value is under 30 characters:Here is how you would do it with model-generated queries:
However, I agree you should be able to specify whether a string value and STRING data type is ASCII or Unicode. Perhaps something like:
I actually solved for this by creating a custom data types folder and I created a STRING_ASCII type so that I would not have to use the “literal” function everywhere.
Then where I initialize Sequelize I import all my custom data types and loop over them to make them available.
I am not sure if I did everything right but its been working for me.
I will say that while being able to extend the data types with your own is a great feature, however, for out of the box support for MSSQL this should not be necessary and this still should be addressed in the core code.
BTW, I got the following to work, which is shorter than the in-model solution presented above: I didn’t need to use the escape function.
lr_rslt_id: { [Op.eq]: Sequelize.literal(
'WBC'
) }I would like to follow same pattern that we already use http://docs.sequelizejs.com/manual/tutorial/models-definition.html#data-types
@dsbert your code works with all the string comparisons, that’s great. But it can cause problems while working with nvarchar column type where we are storing multilingual data(specifically in case of korean, japanese, chinese language).
attaching a monkey patch where we can check column name and decide if we need to remove N or not.
Based on some of the above comments, you can monkey patch this out using the following snippet.
The escaping and ‘N’ prefix are applied based on the result of calling
typeof
against the parameter. By wrapping the parameter in an Object, it avoids the statement that introduces the ‘N’ prefix.Therefore:
becomes:
…and the prefix is gone.
Read through the escape() function to see where it gets applied.
Oh geez what the heck is going on here?! Now I know why the performance of our API has been tanking lately. Wow… Does nobody actually use MSSQL in production? This behavior is positively atrocious.
EDIT: Why is the label on this “feature?” This is a straight-up bug.