NEWID () Nella tabella virtuale unita causa un comportamento incrociato involontario


9

La mia vera query di lavoro era un join interno, ma questo semplice esempio con cross join sembra quasi sempre riprodurre il problema.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT NEWID() TEST_ID
) BB ( B )

Con il mio join interno ho avuto molte righe per le quali ho aggiunto a ciascuno un GUID utilizzando la funzione NEWID () e per circa 9 su 10 di tali righe la moltiplicazione con la tabella virtuale a 2 righe ha prodotto i risultati previsti, solo 2 copie di lo stesso GUID, mentre 1 su 10 produrrebbe risultati diversi. Questo è stato inaspettato a dir poco e mi ha fatto davvero fatica a trovare questo bug nel mio script di generazione dei dati di test.

Se dai un'occhiata alle seguenti query usando anche le funzioni getdate e sysdatetime non deterministiche, non lo vedrai, non lo vedo comunque, vedo sempre lo stesso valore datetime in entrambe le righe del risultato finale.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT GETDATE() TEST_ID
) BB ( B )

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT SYSDATETIME() TEST_ID
) BB ( B )

Attualmente sto usando SQL Server 2008 e il mio lavoro per ora è caricare le mie righe con GUID in una variabile di tabella prima di completare il mio script di generazione casuale dei dati. Una volta che li ho come valori in una tabella rispetto alla tabella virtuale, il problema scompare.

Ho una soluzione alternativa, ma sto cercando i modi per risolvere il problema senza tabelle effettive o variabili di tabella.

Durante la scrittura di questo ho provato senza successo queste possibilità: 1) posizionando newid () in una tabella virtuale nidificata:

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT TEST_ID
    FROM (
        SELECT NEWID() TEST_ID
    ) TT
) BB ( B )

2) racchiudere il newid () all'interno di un'espressione cast come:

SELECT CAST(NEWID() AS VARCHAR(100)) TEST_ID

3) invertire l'ordine di aspetto delle tabelle virtuali all'interno dell'espressione di join

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS JOIN (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

4) utilizzando la croce non correlata si applicano

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

Poco prima di pubblicare finalmente questa domanda, ora ho provato a farlo con successo, a quanto pare, si applica una croce correlata:

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT A
    FROM (
        SELECT 1 UNION ALL
        SELECT 2
    ) TT ( A )
    WHERE BB.B IS NOT NULL
) AA ( A )

Qualcuno ha qualche altra soluzione più elegante, più semplice? In realtà non voglio usare l'applicazione incrociata o la correlazione per una semplice moltiplicazione di righe se non devo.

Risposte:


20

Questo comportamento è in base alla progettazione, come spiegato in dettaglio in questo rapporto sui bug di Connect . La risposta Microsoft più pertinente è riprodotta di seguito per comodità (e nel caso in cui il collegamento scompaia ad un certo punto):

Inserito da Microsoft il 7/7/2008 alle 9:27

Chiusura del ciclo. . . Ho discusso questa domanda con il team Dev. E alla fine abbiamo deciso di non modificare il comportamento attuale, per i seguenti motivi:

  1. L'ottimizzatore non garantisce tempi o numero di esecuzioni di funzioni scalari. Questo è un principio di lunga data. È il "margine di manovra" fondamentale che consente all'ottimizzatore di avere abbastanza libertà per ottenere miglioramenti significativi nell'esecuzione del piano di query.

  2. Questo "comportamento una volta per riga" non è un nuovo problema, sebbene non sia ampiamente discusso. Abbiamo iniziato a modificare il suo comportamento nella versione Yukon. Ma è abbastanza difficile stabilire con precisione, in tutti i casi, esattamente cosa significa! Ad esempio, si applica alle righe intermedie calcolate "lungo la strada" per il risultato finale? - nel qual caso dipende chiaramente dal piano scelto. O si applica solo alle righe che appariranno alla fine nel risultato completato? - c'è una brutta ricorsione in corso qui, poiché sono sicuro che sarai d'accordo!

  3. Come accennato in precedenza, per impostazione predefinita "ottimizziamo le prestazioni", il che è positivo per il 99% dei casi. L'1% dei casi in cui potrebbe cambiare i risultati è abbastanza facile da individuare - "funzioni" con effetti collaterali come NEWID - e facili da "correggere" (di conseguenza il trading di perf). L'impostazione predefinita per "ottimizzare le prestazioni" è di nuovo consolidata e accettata. (Sì, non è la posizione scelta dai compilatori per i linguaggi di programmazione convenzionali, ma così sia).

Quindi, i nostri consigli sono:

  1. Evita di fare affidamento su tempi non garantiti e semantica del numero di esecuzioni.
  2. Evita di usare NEWID () in profondità nelle espressioni di tabella.
  3. Usa OPTION per forzare un comportamento particolare (trading perf)

Spero che questa spiegazione ci aiuti a chiarire le nostre ragioni per chiudere questo bug come "non risolverà".

Le funzioni GETDATEe SYSDATETIMEsono in effetti non deterministiche, ma vengono trattate come costanti di runtime per una particolare query. In generale, ciò significa che il valore della funzione viene memorizzato nella cache all'avvio dell'esecuzione della query e il risultato viene riutilizzato per tutti i riferimenti all'interno della query.

Nessuno dei "workaround" nella domanda è sicuro; non vi è alcuna garanzia che il comportamento non cambierà alla successiva compilazione del piano, alla successiva applicazione di un service pack o di un aggiornamento cumulativo ... o per altri motivi.

L'unica soluzione sicura è utilizzare un oggetto temporaneo di qualche tipo, ad esempio una variabile, una tabella o una funzione multiistruzione. Utilizzare una soluzione alternativa che sembra funzionare oggi in base all'osservazione è un ottimo modo per sperimentare comportamenti imprevisti in futuro, in genere sotto forma di un avviso di paging alle 3 di domenica mattina.


"Nessuno dei" workaround "nella domanda sono sicuri;" idem quello. Quando ho provato ad applicarne uno alla mia effettiva query di lavoro, non mi è stato affatto utile.
JM Hicks,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.