U4-8837 Change paging query for SQL server 2012+ to be more efficient

This commit is contained in:
Shannon
2016-08-09 15:33:35 +02:00
parent 5b88f35471
commit c1da28dd47
6 changed files with 134 additions and 78 deletions

View File

@@ -381,8 +381,6 @@ namespace Umbraco.Core
connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString;
}
Initialize(providerName, connString);
DetermineSqlServerVersion();
}
else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false)
{
@@ -419,8 +417,6 @@ namespace Umbraco.Core
//Remove the legacy connection string, so we don't end up in a loop if something goes wrong.
GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName);
DetermineSqlServerVersion();
}
else
{
@@ -465,48 +461,6 @@ namespace Umbraco.Core
Initialize(providerName);
}
/// <summary>
/// Set the lazy resolution of determining the SQL server version if that is the db type we're using
/// </summary>
private void DetermineSqlServerVersion()
{
var sqlServerSyntax = SqlSyntax as SqlServerSyntaxProvider;
if (sqlServerSyntax != null)
{
//this will not execute now, it is lazy so will only execute when we need to actually know
// the sql server version.
sqlServerSyntax.VersionName = new Lazy<SqlServerVersionName>(() =>
{
try
{
var database = this._factory.CreateDatabase();
var version = database.ExecuteScalar<string>("SELECT SERVERPROPERTY('productversion')");
var firstPart = version.Split('.')[0];
switch (firstPart)
{
case "11":
return SqlServerVersionName.V2012;
case "10":
return SqlServerVersionName.V2008;
case "9":
return SqlServerVersionName.V2005;
case "8":
return SqlServerVersionName.V2000;
case "7":
return SqlServerVersionName.V7;
default:
return SqlServerVersionName.Other;
}
}
catch (Exception)
{
return SqlServerVersionName.Invalid;
}
});
}
}
internal DatabaseSchemaResult ValidateDatabaseSchema()
{

View File

@@ -181,7 +181,7 @@ namespace Umbraco.Core.Persistence
CommonConstruct();
}
enum DBType
internal enum DBType
{
SqlServer,
SqlServerCE,
@@ -726,6 +726,42 @@ namespace Umbraco.Core.Persistence
return true;
}
/// <summary>
/// NOTE: This is a custom mod of PetaPoco!! This builds the paging sql for different db providers
/// </summary>
/// <param name="sql"></param>
/// <param name="sqlSelectRemoved"></param>
/// <param name="sqlOrderBy"></param>
/// <param name="args"></param>
/// <param name="sqlPage"></param>
/// <param name="databaseType"></param>
/// <param name="skip"></param>
/// <param name="take"></param>
internal virtual void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage)
{
if (databaseType == DBType.SqlServer || databaseType == DBType.Oracle)
{
sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, "");
if (rxDistinct.IsMatch(sqlSelectRemoved))
{
sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner";
}
sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}",
sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1);
args = args.Concat(new object[] { skip, skip + take }).ToArray();
}
else if (databaseType == DBType.SqlServerCE)
{
sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1);
args = args.Concat(new object[] { skip, take }).ToArray();
}
else
{
sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1);
args = args.Concat(new object[] { take, skip }).ToArray();
}
}
public void BuildPageQueries<T>(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage)
{
// Add auto select clause
@@ -734,34 +770,12 @@ namespace Umbraco.Core.Persistence
// Split the SQL into the bits we need
string sqlSelectRemoved, sqlOrderBy;
if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy))
if (SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy) == false)
throw new Exception("Unable to parse SQL statement for paged query");
if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*"))
throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id");
// Build the SQL for the actual final result
if (_dbType == DBType.SqlServer || _dbType == DBType.Oracle)
{
sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, "");
if (rxDistinct.IsMatch(sqlSelectRemoved))
{
sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner";
}
sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}",
sqlOrderBy==null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1);
args = args.Concat(new object[] { skip, skip+take }).ToArray();
}
else if (_dbType == DBType.SqlServerCE)
{
sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1);
args = args.Concat(new object[] { skip, take }).ToArray();
}
else
{
sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1);
args = args.Concat(new object[] { take, skip }).ToArray();
}
BuildSqlDbSpecificPagingQuery(_dbType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
}
// Fetch a page
@@ -2315,8 +2329,8 @@ namespace Umbraco.Core.Persistence
// Member variables
string _connectionString;
string _providerName;
readonly string _connectionString;
readonly string _providerName;
DbProviderFactory _factory;
IDbConnection _sharedConnection;
IDbTransaction _transaction;

View File

@@ -203,7 +203,8 @@ namespace Umbraco.Core.Persistence
{
//if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts.
var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider;
if ((sqlServerSyntax != null && (int)sqlServerSyntax.VersionName.Value < (int)SqlServerVersionName.V2008)
if ((sqlServerSyntax != null && (int)sqlServerSyntax.GetVersionName(db) < (int)SqlServerVersionName.V2008)
|| SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider)
{
//SqlCe doesn't support bulk insert statements!

View File

@@ -14,12 +14,57 @@ namespace Umbraco.Core.Persistence.SqlSyntax
public SqlServerSyntaxProvider()
{
}
}
/// <summary>
/// Gets/sets the version of the current SQL server instance
/// </summary>
internal Lazy<SqlServerVersionName> VersionName { get; set; }
internal SqlServerVersionName GetVersionName(Database database)
{
if (_versionName.HasValue)
return _versionName.Value;
try
{
var version = database.ExecuteScalar<string>("SELECT SERVERPROPERTY('productversion')");
var firstPart = version.Split('.')[0];
switch (firstPart)
{
case "13":
_versionName = SqlServerVersionName.V2014;
break;
case "12":
_versionName = SqlServerVersionName.V2014;
break;
case "11":
_versionName = SqlServerVersionName.V2012;
break;
case "10":
_versionName = SqlServerVersionName.V2008;
break;
case "9":
_versionName = SqlServerVersionName.V2005;
break;
case "8":
_versionName = SqlServerVersionName.V2000;
break;
case "7":
_versionName = SqlServerVersionName.V7;
break;
default:
_versionName = SqlServerVersionName.Other;
break;
}
}
catch (Exception)
{
_versionName = SqlServerVersionName.Invalid;
}
return _versionName.Value;
}
private SqlServerVersionName? _versionName;
/// <summary>
/// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only

View File

@@ -3,6 +3,9 @@
/// <summary>
/// Represents the version name of SQL server (i.e. the year 2008, 2005, etc...)
/// </summary>
/// <remarks>
/// see: https://support.microsoft.com/en-us/kb/321185
/// </remarks>
internal enum SqlServerVersionName
{
Invalid = -1,
@@ -11,6 +14,8 @@
V2005 = 2,
V2008 = 3,
V2012 = 4,
Other = 5
V2014 = 5,
V2016 = 6,
Other = 100
}
}

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Text;
using StackExchange.Profiling;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
{
@@ -154,5 +155,41 @@ namespace Umbraco.Core.Persistence
}
base.OnExecutedCommand(cmd);
}
/// <summary>
/// We are overriding this in the case that we are using SQL Server 2012+ so we can make paging more efficient than the default PetaPoco paging
/// see: http://issues.umbraco.org/issue/U4-8837
/// </summary>
/// <param name="sql"></param>
/// <param name="sqlSelectRemoved"></param>
/// <param name="sqlOrderBy"></param>
/// <param name="args"></param>
/// <param name="sqlPage"></param>
/// <param name="databaseType"></param>
/// <param name="skip"></param>
/// <param name="take"></param>
internal override void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage)
{
if (databaseType == DBType.SqlServer)
{
//we need to check it's version to see what kind of paging format we can use
//TODO: This is a hack, but we don't have access to the SqlSyntaxProvider here, we can in v8 but not now otherwise
// this would be a breaking change.
var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider;
if (sqlServerSyntax != null)
{
if ((int) sqlServerSyntax.GetVersionName(this) >= (int) SqlServerVersionName.V2012)
{
//we can use the good paging! to do that we are going to change the databaseType to SQLCE since
//it also uses the good paging syntax.
base.BuildSqlDbSpecificPagingQuery(DBType.SqlServerCE, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
return;
}
}
}
//use the defaults
base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
}
}
}