Pergunta

funções Por que escalar-avaliadas parecem consultas causa para executar cumulativamente mais lento quanto mais vezes em sucessão que eles são usados?

Eu tenho esta tabela que foi construída com dados adquiridos a partir de um terceiro partido.

Eu aparadas algumas coisas para fazer este post curto ... mas só para você ter uma idéia de como as coisas são configurados.

CREATE TABLE [dbo].[GIS_Location](
        [ID] [int] IDENTITY(1,1) NOT NULL, --PK
        [Lat] [int] NOT NULL,
        [Lon] [int] NOT NULL,
        [Postal_Code] [varchar](7) NOT NULL,
        [State] [char](2) NOT NULL,
        [City] [varchar](30) NOT NULL,
        [Country] [char](3) NOT NULL,

CREATE TABLE [dbo].[Address_Location](
    [ID] [int] IDENTITY(1,1) NOT NULL, --PK
    [Address_Type_ID] [int] NULL,
    [Location] [varchar](100) NOT NULL,
    [State] [char](2) NOT NULL,
    [City] [varchar](30) NOT NULL,
    [Postal_Code] [varchar](10) NOT NULL,
    [Postal_Extension] [varchar](10) NULL,
    [Country_Code] [varchar](10) NULL,

Então eu tenho duas funções que olhar para cima LAT e LON.

CREATE FUNCTION [dbo].[usf_GIS_GET_LAT]
(
    @City VARCHAR(30),
    @State CHAR(2)
)
RETURNS INT 
WITH EXECUTE AS CALLER
AS
BEGIN
    DECLARE @LAT INT

    SET @LAT = (SELECT TOP 1 LAT FROM GIS_Location WITH(NOLOCK) WHERE [State] = @State AND [City] = @City)

RETURN @LAT
END


CREATE FUNCTION [dbo].[usf_GIS_GET_LON]
(
    @City VARCHAR(30),
    @State CHAR(2)
)
RETURNS INT 
WITH EXECUTE AS CALLER
AS
BEGIN
    DECLARE @LON INT

    SET @LON = (SELECT TOP 1 LON FROM GIS_Location WITH(NOLOCK) WHERE [State] = @State AND [City] = @City)

RETURN @LON
END

Quando eu execute o seguinte ...

SET STATISTICS TIME ON

SELECT
    dbo.usf_GIS_GET_LAT(City,[State]) AS Lat,
    dbo.usf_GIS_GET_LON(City,[State]) AS Lon
FROM
    Address_Location WITH(NOLOCK)
WHERE
    ID IN (SELECT TOP 100 ID FROM Address_Location WITH(NOLOCK) ORDER BY ID DESC)

SET STATISTICS TIME OFF

100 = ~ 8 ms, 200 ~ = 32 ms, 400 ms ~ = 876

- Edit Desculpe, eu deveria ter sido mais clara. Eu não estou olhando para ajustar a consulta listados acima. Este é apenas um exemplo para mostrar o tempo de execução ficando mais lento dos mais registros que crunches completamente. Na aplicação mundo real as funções são usadas como parte de um onde cláusula para construir um raio em torno de uma cidade e estado para incluir todos os registros com naquela região.

Foi útil?

Solução

Na maioria dos casos, é melhor para funções valorizadas evitar escalares que as tabelas de referência, porque (como outros disseram que) eles são basicamente caixas pretas que precisam ser executado uma vez para cada linha, e não pode ser otimizado pelo motor plano de consulta. Portanto, eles tendem a dimensionar linearmente, mesmo se as tabelas associadas têm índices.

Você pode querer considerar o uso de uma função-inline-table valorizado, uma vez que eles são avaliados em linha com a consulta, e pode ser otimizada. Você começa o encapsulamento que quiser, mas o desempenho de colar as expressões direita na instrução select.

Como um efeito colateral de ser embutido, que não pode conter qualquer código de procedimento (não declare @variable; definir @variable = ..; retorno). No entanto, eles podem retornar várias linhas e colunas.

Você poderia re-escrever suas funções algo como isto:

create function usf_GIS_GET_LAT(
    @City varchar (30),
    @State char (2)
)
returns table
as return (
  select top 1 lat
  from GIS_Location with (nolock) 
  where [State] = @State
    and [City] = @City
);

GO

create function usf_GIS_GET_LON (
    @City varchar (30),
    @State char (2)
)
returns table
as return (
  select top 1 LON
  from GIS_Location with (nolock)
  where [State] = @State
    and [City] = @City
);

A sintaxe de usá-los também é um pouco diferente:

select
    Lat.Lat,
    Lon.Lon
from
    Address_Location with (nolock)
    cross apply dbo.usf_GIS_GET_LAT(City,[State]) AS Lat
    cross apply dbo.usf_GIS_GET_LON(City,[State]) AS Lon
WHERE
    ID IN (SELECT TOP 100 ID FROM Address_Location WITH(NOLOCK) ORDER BY ID DESC)

Outras dicas

Eles não.

Não há erro em funções escalares que faz com que seu desempenho para degradar de forma exponencial, dependendo do número de linhas na função escalar é executada contra. Tente seus testes novamente e ter um olhar para profiler do SQL, olhando para a CPU e lê e colunas de duração. Aumentar-lhe o tamanho de teste para incluir testes que levam mais de um segundo, dois segundos, cinco segundos.

CREATE FUNCTION dbo.slow
(
    @ignore int
)
RETURNS INT 
AS
BEGIN
    DECLARE @slow INT
    SET @slow = (select count(*) from sysobjects a 
        cross join sysobjects b 
        cross join sysobjects c 
        cross join sysobjects d 
        cross join sysobjects e 
        cross join sysobjects f
    where a.id = @ignore) 

    RETURN @slow
END
go
SET STATISTICS TIME ON

select top 1 dbo.slow(id)
from sysobjects
go
select top 5 dbo.slow(id)
from sysobjects
go
select top 10 dbo.slow(id)
from sysobjects
go
select top 20 dbo.slow(id)
from sysobjects
go
select top 40 dbo.slow(id)
from sysobjects

SET STATISTICS TIME OFF

saída

SQL Server Execution Times:
   CPU time = 203 ms,  elapsed time = 202 ms.


SQL Server Execution Times:
   CPU time = 889 ms,  elapsed time = 939 ms.

SQL Server Execution Times:
   CPU time = 1748 ms,  elapsed time = 1855 ms.

SQL Server Execution Times:
   CPU time = 3541 ms,  elapsed time = 3696 ms.


SQL Server Execution Times:
   CPU time = 7207 ms,  elapsed time = 7392 ms.

Tenha em mente que se você estiver executando uma função escalar contra linhas no conjunto de resultados, a função escalar será executado por linha sem otimização global.

Você pode envolver sua funcionalidade em um TVF em linha, que será muito mais rápido:

http : //sqlblog.com/blogs/alexander_kuznetsov/archive/2008/05/23/reuse-your-code-with-cross-apply.aspx

você chamar a função duas vezes (dois hits selecionados para o DB) para cada linha no conjunto de resultados.

para tornar a sua consulta mais rápida juntar-te a GIS_Location e ignorar as funções:

SELECT
    g.Lat,
    g.Lon
FROM
    Address_Location        l WITH(NOLOCK)
    INNER JOIN GIS_Location g WITH(NOLOCK) WHERE l.State = g.State AND l.City = g.City
WHERE
    ID IN (SELECT TOP 100 ID FROM Address_Location WITH(NOLOCK) ORDER BY ID DESC)

Eu não tenho certeza por que o NOLOCK ou o louco onde cláusula, eu simplesmente copiado da pergunta ...

Em termos simples, porque as expressões SQL com funções definidas pelo usuário são menos eficientes do que expressões SQL sem eles. A lógica de execução não pode ser otimizado; ea sobrecarga de função (incluindo protocolos de chamada) devem ser incorridos para cada linha.

O conselho de KMike é bom. ONDE .. IN (SELECT algo) não é provável que seja um padrão eficiente, e neste caso pode ser facilmente substituído com um JOIN.

Veja se isso funciona melhor ... Ou talvez um distinto junção interna?

select a.*,
(select top 1 g.Lat from GIS_Location g where g.City = a.City and g.State = a.State) as Lat,
(select top 1 g.Lon from GIS_Location g where g.City = a.City and g.State = a.State) as Lon
from Address_Location a
where a.ID in (select top 100 ID from Address_Location order by ID desc)

Quanto ao desempenho da função escalar, eu não tenho certeza.

funções escalares Tipicamente são muito mais lento do que homólogos TVF inline. Felizmente para muitos cenários isso vai mudar.

SQL Server 2019 irá introduzir Scalar UDF Inlining :

Uma característica sob a suíte de processamento de consulta inteligente dos recursos. Este recurso melhora o desempenho das consultas que invocam UDFs escalares em SQL Server (começando com SQL Server 2019 pré-visualização)

T-SQL escalares Funções definidas pelo usuário

Funções definidas pelo usuário que são implementadas em Transact-SQL e retornar um único valor de dados são referidos como T-SQL escalares funções definidas pelo usuário. UDFs T-SQL são uma maneira elegante para alcançar a reutilização de código e modularidade através de consultas SQL. Alguns cálculos (tais como regras de negócio complexas) são mais fáceis de expressar em forma UDF imperativo. UDFs ajuda na construção de lógica complexa sem a necessidade de experiência em escrever consultas SQL complexas.

escalares FDU tipicamente acabam com um mau desempenho, devido às seguintes razões.

  • iterativo invocação
  • A falta de custando
  • execução Interpretado
  • execução Serial

Inlining automática de escalar UDFs

O objetivo do recurso inlining Scalar UDF é para melhorar o desempenho de consultas que invocam UDFs escalares T-SQL, onde a execução UDF é o principal gargalo.

Com este novo recurso, UDFs escalares são automaticamente transformadas em expressões escalares ou subconsultas escalares que são substituídos na consulta chamando no lugar do operador UDF. Estas expressões e subconsultas são então otimizados. Como resultado, o plano de consulta não terá um operador de função definida pelo usuário, mas seus efeitos serão observados no plano, como visões ou TVFs inline.


requisitos Inlineable Scalar UDF

Um escalar T-SQL UDF pode ser em linha, se todas as seguintes condições são verdadeiras:

  • O UDF é escrito usando as seguintes construções:

    1. DECLARE, SET:. Variável declaração e atribuições
    2. SELECT:. Consulta SQL com / assignments1 única variável múltipla
    3. if / else:. Ramificação com níveis arbitrários de nidificação
    4. RETURN:. Únicos ou múltiplos comandos return
    5. UDF: Nested / calls2 recursiva função
    6. .
    7. Outros:. Operações relacionais, como existe, ISNULL
  • O UDF não invoca qualquer função intrínseca que é ou (tais como GETDATE ()) ou tem effects3 lado dependente do tempo (tal como NEWSEQUENTIALID ()).

  • O UDF usa a cláusula EXECUTE AS CALLER (o comportamento padrão se a cláusula EXECUTE AS não é especificada).
  • O UDF não variáveis ??de tabela de referência ou parâmetros com valor de tabela.
  • A consulta invocar uma UDF escalar não faz referência a uma chamada UDF escalar em sua cláusula GROUP BY.
  • O UDF não é compilado nativamente (interoperabilidade é suportado).
  • O UDF não é usada em uma coluna computada ou uma definição de restrição de verificação.
  • O UDF não faz referência a tipos definidos pelo usuário.
  • Não há assinaturas adicionadas ao UDF.
  • O UDF não é uma função de partição.

Verificar se a função é elegíveis para uso inline:

SELECT OBJECT_NAME([object_id]) AS name, is_inlineable
FROM sys.sql_modules
WHERE [object_id] = OBJECT_ID('schema.function_name')

Como ativar / desativar o recurso no nível de banco de dados:

ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = ON/OFF;
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top