SQL Server öngörülemeyen seçim sonuçları (dbms hatası?)


37

Aşağıda, garip sonuçlar veren, tahmin edilemeyen ve ekibimizde açıklayamadığımız basit bir örnek verilmiştir. Yanlış bir şey mi yapıyoruz yoksa SQL Server hatası mı?

Bazı araştırmalardan sonra, arama alanını "erkekler" tablosundan bir kayıt seçen alt sorgudaki sendika yan tümcesine düşürdük

SQL Server 2000'de beklendiği gibi çalışır (12 satır döndürür), ancak 2008 ve 2012'de yalnızca bir satır döndürür.

create table dual (dummy int)

insert into dual values (0)

create table men (
man_id int,
wife_id int )

-- there are 12 men, 6 married 
insert into men values (1, 1)
insert into men values (2, 2)
insert into men values (3, null)
insert into men values (4, null)
insert into men values (5, null)
insert into men values (6, 3)
insert into men values (7, 5)
insert into men values (8, 7)
insert into men values (9, null)
insert into men values (10, null)
insert into men values (11, null)
insert into men values (12, 9)

Bu sadece bir satır döndürür: 1 1 2

select 
man_id,
wife_id,
(select count( * ) from 
    (select dummy from dual
     union select men.wife_id  ) family_members
) as family_size
from men
--where wife_id = 2 -- uncomment me and try again

Uncomment son satırı ve verir: 2 2 2

Çok garip davranış var:

  • Bir dizi damladan sonra, "erkekler" masasında yaratır, keser ve ekler bazen işe yarar (12 satır döndürür)
  • "Union select men.wife_id" i değiştirdiğinizde "union all men.wife_id" veya "union select isnull (men.wife_id, null)" (!!!) , 12 satır döndürür (beklendiği gibi).
  • Tuhaf davranışın "wife_id" sütununun veri türü ile ilgisi olmadığı görülüyor. Geliştirme sisteminde çok daha büyük veri setleri ile gözlemledik.
  • "where wife_id> 0" 6 satır döndürüyor
  • biz de bu tür ifadelerle görüşlerin garip davranışlarını gözlemliyoruz. SELECT *, satırların alt kümesini, SELECT TOP 1000 ise hepsini döndürür

Yanıtlar:


35

Yanlış bir şey mi yapıyoruz yoksa SQL Server hatası mı?

Her zamanki destek kanalınız üzerinden bildirmeniz gereken yanlış sonuçlardan oluşan bir hatadır. Bir destek sözleşmeniz yoksa, Microsoft davranışı bir hata olarak onaylarsa, ödemeli olayların normalde geri ödendiğini bilmek yardımcı olabilir .

Böcek üç bileşen gerektirir:

  1. Dış referansı olan iç içe döngüler (bir başvuru)
  2. Dış referansı arayan bir iç taraf tembel indeks makarası
  3. Bir iç taraf birleştirme işleci

Örneğin, sorudaki sorgu aşağıdakine benzer bir plan oluşturur:

Açıklamalı plan

Bu öğelerden birini kaldırmanın birçok yolu vardır, böylece hata artık çoğaltılmaz.

Örneğin, bir kişi optimize edicinin Lazy Index Spool kullanmamayı seçtiği anlamına gelen endeksler veya istatistikler yaratabilir. Veya bir birleştirme veya birleştirme birliğini zorlamak için Birleştirme'yi kullanmak yerine ipuçlarını kullanabilir. Biri aynı semantiği ifade etmek için sorguyu da yeniden yazabilir, ancak gerekli elemanlardan bir veya daha fazlasının eksik olduğu farklı bir plan şeklinde sonuçlanır.

Daha fazla detay

Bir Lazy Index Spool, dış referans (ilişkili parametre) değerleri ile indekslenmiş bir çalışma tablosunda, iç taraf sonuç satırlarını tembel bir şekilde önler. Eğer bir Lazy Index Spool'dan daha önce gördüğü bir dış referans istenirse, önbelleğe alınmış sonuç satırını çalışma masasından alır ("geri sar"). Makaraya daha önce görmediği bir dış referans değeri istenirse, alt ağacını geçerli dış referans değeriyle çalıştırır ve sonucu önbelleğe alır (bir "yeniden bağlama"). Lazy Index Spool'daki arama yordamı, çalışma tablosu için anahtar (lar) ı gösterir.

Sorun, bu özel plan şeklinde, makara yeni bir dış referansın daha önce gördüğü ile aynı olup olmadığını görmek için kontrol ettiğinde ortaya çıkar. Yuvalanmış Döngüler Birleşimi dış referanslarını doğru günceller ve PrepRecomputearayüz giriş metotları ile operatörleri kendi iç girişinde bilgilendirir . Bu kontrolün başında, iç taraf operatörleri CParamBounds:FNeedToReloaddış referansın son zamandan itibaren değişip değişmediğini görmek için özelliği okurlar . Örnek bir yığın izi aşağıda gösterilmiştir:

CParamBounds: FNeedToReload

Yukarıda gösterilen alt ağaç, özellikle birleştirme işleminin kullanıldığı yerlerde CParamBounds:FNeedToReload, dış referansın gerçekten değişip değişmediğine bakılmaksızın, her zaman yanlış döndüren ciltlemelerde bir şeyler ters gider (belki bir ByVal / ByRef / Kopyalama sorunu) .

Aynı alt ağaç bulunduğunda, ancak Bir Birlik Birliği veya Karma Birlik kullanıldığında, bu temel özellik her yinelemede doğru bir şekilde ayarlanır ve Tembel Endeks Biriktiricisi her seferinde uygun şekilde geri sarılır veya yeniden bağlanır. Bu arada, Ayırt Edici Sıralama ve Akış Toplamı suçsuzdur. Benim şüphem, Birleştirme ve Hash Birliği'nin önceki değerin bir kopyasını oluşturması, Oysa Birleştirme referans kullanıyor. Maalesef SQL Server kaynak koduna erişmeden bunu doğrulamak neredeyse imkansız.

Net sonuç, sorunlu plan şeklindeki Tembel Endeks Makarası'nın daima mevcut dış referansı görmüş olduğunu düşündüğü, çalışma masasını arayarak geri sardığı, genellikle hiçbir şey bulamadığı, yani o dış referans için hiçbir satırın geri dönmediğidir. Bir hata ayıklayıcısındaki yürütme adımlarında, makara, yalnızca RewindHelperyöntemini çalıştırır ve asla ReloadHelperyöntemini (yeniden yükleme = bu bağlamda yeniden bağlama). Bu, yürütme planında belirgindir, çünkü makara altındaki operatörlerin hepsinde 'İşlem Sayısı = 1' vardır.

RewindHelper

İstisna, elbette, Tembel Endeks Makarası'nın ilk dış referansı için. Bu her zaman alt ağacı çalıştırır ve çalışma tablosunda bir sonuç satırını önbelleğe alır. İzleyen tüm yinelemeler, bir geri dönüşle sonuçlanır; geçerli yineleme, ilk kez etrafındaki dış referans için aynı değere sahip olduğunda yalnızca bir satır (tek önbelleğe alınmış satır) oluşturur.

Bu nedenle, Yuvalanmış Döngüler Birleşmesinin dış tarafında ayarlanan herhangi bir giriş için, sorgu, işlenen ilk satırın kopyaları olduğu kadar çok sayıda satır döndürür (elbette ilk satır için bir tane).

gösteri

Tablo ve örnek veriler:

CREATE TABLE #T1 
(
    pk integer IDENTITY NOT NULL,
    c1 integer NOT NULL,

    CONSTRAINT PK_T1
    PRIMARY KEY CLUSTERED (pk)
);
GO
INSERT #T1 (c1)
VALUES
    (1), (2), (3), (4), (5), (6),
    (1), (2), (3), (4), (5), (6),
    (1), (2), (3), (4), (5), (6);

Aşağıdaki (önemsiz) sorgu, Bir Birlik Birliği kullanarak her satır için (toplamda 18) iki doğru sayı üretir:

SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY 
(
    SELECT COUNT_BIG(*) AS c1
    FROM
    (
        SELECT T1.c1
        UNION
        SELECT NULL
    ) AS U
) AS C;

Birlik Birliği Planı

Şimdi bir birleştirme zorlamak için bir sorgu ipucu eklersek:

SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY 
(
    SELECT COUNT_BIG(*) AS c1
    FROM
    (
        SELECT T1.c1
        UNION
        SELECT NULL
    ) AS U
) AS C
OPTION (CONCAT UNION);

Uygulama planı sorunlu bir şekle sahiptir:

Birleştirme Planı

Ve sonuç şimdi yanlıştır, sadece üç satır:

Üç satır sonucu

Bu davranış garanti edilmese de, Kümelenmiş Dizin Taraması'ndan ilk satırın c1değeri 1'dir. Bu değere sahip iki satır daha vardır, bu nedenle toplamda üç satır üretilir.

Şimdi veri tablosunu kesin ve 'ilk' satırının daha fazla kopyasıyla yükleyin:

TRUNCATE TABLE #T1;

INSERT #T1 (c1)
VALUES
    (1), (2), (3), (4), (5), (6),
    (1), (2), (3), (4), (5), (6),
    (1), (1), (1), (1), (1), (1);

Şimdi birleştirme planı:

8 satır Birleştirme Planı

Ve belirtildiği gibi, c1 = 1elbette ki , 8 satır üretilir :

8 satır sonucu

Bu hata için bir Connect öğesi açtığınızı fark ediyorum ama bu gerçekten de, üretim etkisi olan sorunları bildirmenin yeri değil. Bu durumda, gerçekten Microsoft Destek ile iletişim kurmanız gerekir.


Bu yanlış sonuç hatası bir aşamada giderildi. Artık 2012'den itibaren benim için hiçbir SQL Server sürümünde çoğaltılmıyor. SQL Server 2008 R2 SP3-GDR sürüm 10.50.6560.0 (X64) üzerinde repro yapar.


-3

Neden from ifadesi olmadan bir alt sorgu kullanıyorsunuz? Bunun 2005 ve 2008 sunucularında farklılığa neden olabileceğini düşünüyorum. Belki açık bir şekilde katılabilirsiniz.

select 
m1.man_id,
m1.wife_id,
(select count( * ) from 
    (select dummy from dual
     union
     select m2.wife_id
     from men m2
     where m2.man_id = m1.man_id) family_members
) as family_size
from men m1

3
Evet, bu işe yarıyor ama benim sürümüm de çalışmalı. Özet örneğin üzerinde, üretim sorgumuzun daha basit bir şekli var, bu da çok daha mantıklı.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.