“Önizleme modu” ile veritabanı saklı yordamı


15

Birlikte çalıştığım veritabanı uygulamasında oldukça yaygın bir örüntü, "önizleme modu" olan bir rapor veya yardımcı program için saklı yordam oluşturma gereksinimidir. Böyle bir yordam güncelleştirme yaptığında, bu parametre eylemin sonuçlarının döndürülmesi gerektiğini, ancak yordamın veritabanındaki güncelleştirmeleri gerçekten gerçekleştirmemesi gerektiğini belirtir.

Bunu yapmanın bir yolu if, parametre için bir deyim yazmak ve iki tam kod bloğuna sahip olmaktır ; bunlardan biri verileri günceller ve döndürür, diğeri ise sadece verileri döndürür. Ancak, kod çoğaltma ve önizleme verilerinin aslında bir güncellemeyle ne olacağının doğru bir yansıması olduğuna dair nispeten düşük bir güven derecesi nedeniyle bu istenmeyen bir durumdur.

Aşağıdaki örnek, canlı güncelleme modu olarak önizleme modu için yalnızca tek bir kod bloğunu kullanmak için işlem kayıt noktalarını ve değişkenlerini (işlemlerden etkilenmeyen geçici tabloların aksine) kullanmaya çalışır.

Not: Bu yordam çağrısının kendisi bir işleme yuvalanmış olabileceğinden, işlem geri almaları bir seçenek değildir. Bu SQL Server 2012'de test edilmiştir.

CREATE TABLE dbo.user_table (a int);
GO

CREATE PROCEDURE [dbo].[PREVIEW_EXAMPLE] (
  @preview char(1) = 'Y'
) AS

CREATE TABLE #dataset_to_return (a int);

BEGIN TRANSACTION; -- preview mode required infrastructure
  DECLARE @output_to_return TABLE (a int);
  SAVE TRANSACTION savepoint;

  -- do stuff here
  INSERT INTO dbo.user_table (a)
    OUTPUT inserted.a INTO @output_to_return (a)
    VALUES (42);

  -- catch preview mode
  IF @preview = 'Y'
    ROLLBACK TRANSACTION savepoint;

  -- save output to temp table if used for return data
  INSERT INTO #dataset_to_return (a)
  SELECT a FROM @output_to_return;
COMMIT TRANSACTION;

SELECT a AS proc_return_data FROM #dataset_to_return;
RETURN 0;
GO

-- Examples
EXEC dbo.PREVIEW_EXAMPLE @preview = 'Y';
SELECT a AS user_table_after_preview_mode FROM user_table;

EXEC dbo.PREVIEW_EXAMPLE @preview = 'N';
SELECT a AS user_table_after_live_mode FROM user_table;

-- Cleanup
DROP TABLE dbo.user_table;
DROP PROCEDURE dbo.PREVIEW_EXAMPLE;
GO

Bu kod ve tasarım deseni hakkında geri bildirim arıyorum ve / veya aynı soruna başka çözümler farklı formatlarda varsa.

Yanıtlar:


12

Bu yaklaşımın birkaç kusuru vardır:

  1. "Önizleme" terimi, çalıştırılmakta olan verinin niteliğine (ve işlemden işleme değişir) bağlı olarak çoğu durumda oldukça yanıltıcı olabilir. Üzerinde çalışmakta olan mevcut verinin, "önizleme" verilerinin toplandığı zaman ile kullanıcı 15 dakika sonra geri geldiğinde - biraz kahve tuttuktan sonra, bir duman için dışarı çıkıp yürürken aynı durumda olmasını sağlamak için ne gerekir? blok etrafında, geri geliyor ve eBay bir şey kontrol - ve aslında işlemi gerçekleştirmek için "Tamam" düğmesini tıklatın ve böylece sonunda düğmesini tıkladığını fark eder?

    Önizleme oluşturulduktan sonra işleme devam etmek için bir zaman sınırınız var mı? Ya da muhtemelen verinin başlangıçtaki ile aynı durumda olduğunu belirlemenin bir yolu SELECT?

  2. Örnek kod aceleyle yapılmış olabilir ve gerçek bir kullanım durumunu temsil etmeyebilir, ancak neden bir INSERTişlem için "Önizleme" olur ? Bu gibi bir şeyle birden çok satır eklerken mantıklı olabilir INSERT...SELECTve değişken sayıda satır eklenebilir, ancak bu tek bir işlem için fazla bir anlam ifade etmez.

  3. önizleme verilerinin aslında bir güncellemeyle ne olacağının doğru bir yansıması olduğuna dair nispeten düşük bir güven derecesi nedeniyle bu istenmeyen bir durumdur.

    Bu "düşük güven derecesi" tam olarak nereden geliyor? SELECTBirden çok tablo birleştirildiğinde ve sonuç kümesinde satırların çoğaltılması söz konusu olduğunda , gösterilenden farklı sayıda satırı güncellemek mümkün olsa da, burada bir sorun olmamalıdır. Birinden etkilenmesi gereken satırlar UPDATEkendi başlarına seçilebilir. Bir uyumsuzluk varsa, sorguyu yanlış yapıyorsunuz.

    Ve güncellenecek tabloda birden çok satırla eşleşen bir JOINed tablo nedeniyle çoğaltma olduğu durumlar "Önizleme" oluşturulacak durumlar değildir. Ve durumun böyle olduğu bir durum varsa, kullanıcıya raporda yinelenen raporun bir alt kümesini güncellendikleri açıklanmalıdır, böylece biri yalnızca bir hata ise görünmez etkilenen satırların sayısına bakın.

  4. Tamlık uğruna (bundan bahsi geçen diğer cevaplar olsa da), TRY...CATCHbu çağrıları iç içe yerleştirirken (Kaydetme Noktaları kullanmasa bile ve İşlemler kullanmasa bile) yapıyla kolayca karşılaşabilecek şekilde yapıyı kullanmıyorsunuzdur. İç içe Saklı Yordam çağrıları arasındaki işlemleri işleyen bir şablon için lütfen DBA.SE adresindeki aşağıdaki Soruya verdiğim yanıtı inceleyin:

    İşlemi C # Kodunda ve saklı yordamda işlememiz gerekiyor mu

  5. DAHİ sorunlar yukarıda hesabının verildiğini kaydetti kritik bir kusur hala var: zaman kısa süre için operasyon (yani önce gerçekleştirilmekte olan ROLLBACK(sorguları kullanarak) Herhangi kirli okunan sorgular WITH (NOLOCK)veya SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDveri yakala) o bir an sonra yok mu. Kirli-okuma sorguları kullanan herkes bunun farkında olmalı ve bu olasılığı kabul etmiş olsa da, bunun gibi işlemler hata ayıklaması çok zor olan veri anormalliklerini ortaya koyma şansını büyük ölçüde arttırır (yani: ne kadar zaman harcamak istersiniz belirgin bir doğrudan nedeni olmayan bir sorun mu buldunuz?).

  6. Bunun gibi bir model, hem daha fazla kilit alarak engellemeyi artırarak hem de daha fazla İşlem Günlüğü etkinliği oluşturarak sistem performansını düşürür. (Şimdi görüyorum ki @MartinSmith, bu 2 konudan da Soru üzerine bir yorumda bahsetti.)

    Ayrıca, değiştirilen tablolarda Tetikleyiciler varsa, bu gereksiz olan biraz ek işlem (CPU ve Fiziksel / Mantıksal okumalar) olabilir. Tetikleyiciler ayrıca kirli okumalardan kaynaklanan veri anormalliklerini de artıracaktır.

  7. Doğrudan yukarıda belirtilen nokta ile ilgili olarak - artan kilitler - İşlemin kullanımı, özellikle Tetikleyiciler söz konusu olduğunda, kilitlenmeye girme olasılığını artırır.

  8. Yalnızca daha az olası INSERTişlem senaryosuyla ilgili olması gereken daha az ciddi bir sorun : "Önizleme" verileri, DEFAULTKısıtlamalar ( Sequences/ NEWID()/ NEWSEQUENTIALID()) ve tarafından belirlenen sütun değerlerine ilişkin olarak eklenenlerle aynı olmayabilir IDENTITY.

  9. Tablo Değişkeni içeriğinin Geçici Tabloya yazılması için ek yüke gerek yoktur. ROLLBACKBasitçe daha mantıklı olurdu böylece, (eğer ilk etapta Tablo Değişkenler kullandıklarını söyledi neden olan) Tablo Değişken verileri etkilemeyeceğini SELECT FROM @output_to_return;sonunda ve daha sonra da geçici oluşturarak rahatsız etmeyin Tablo.

  10. Bu Kayıt Noktaları nüansının bilinmemesi durumunda (yalnızca tek bir Saklı Yordam gösterdiği için örnek koddan söylemek zor): işlemin beklediğiniz gibi davranması için benzersiz Kaydetme Noktası adları kullanmanız gerekir ROLLBACK {save_point_name}. Adları yeniden kullanırsanız, bir ROLLBACK, o adın en son Kaydetme Noktasını geri alır; bu ad, ROLLBACKçağrıldığı yerde aynı yuvalama düzeyinde olmayabilir . Bu davranışı çalışırken görmek için lütfen aşağıdaki yanıttaki ilk örnek kod bloğuna bakın: Saklı yordamda işlem

Bunun anlamı şudur:

  • "Önizleme" yapmak, kullanıcılara yönelik işlemler için çok anlamlı değildir. Bunu bakım işlemleri için sık sık yaparım, böylece işleme devam edersem ne silineceğini / Çöp Toplanır'ı görebilirim. Ben denilen isteğe bağlı bir parametre ekleyin @TestModeve başka IFbir SELECTzaman yapar bir deyimi yapın . Parametreyi uygulama tarafından çağrılan Saklı Yordamlara ekliyorum, böylece ben (ve diğerleri) verilerin durumunu etkilemeden basit testler yapabilir, ancak bu parametre asla uygulama tarafından kullanılmaz.@TestMode = 1DELETE@TestMode

  • Bunun "sorunların" üst kısmından net olmaması durumunda:

    DML ifadesi yürütülecekse neyin etkilenmesi gerektiğini görmek için bir "Önizleme" / "Test" moduna ihtiyacınız varsa / istiyorsanız, bunu gerçekleştirmek için İşlemleri (örn. BEGIN TRAN...ROLLBACKKalıp) KULLANMAYIN . En iyi ihtimalle sadece tek kullanıcılı bir sistemde çalışan bir modeldir ve bu durumda iyi bir fikir bile yoktur.

  • İfadenin iki dalı arasında sorgunun büyük bölümünün tekrarlanması, IFher değişiklik yapıldığında her ikisinin de güncellenmesi gerektiğinde potansiyel bir sorun yaratır. Ancak, iki sorgu arasındaki farklar genellikle bir kod incelemesinde yakalanması ve düzeltilmesi kolaydır. Diğer yandan, devlet farklılıkları ve kirli okumalar gibi problemleri bulmak ve düzeltmek çok daha zordur. Ve düşük sistem performansı sorununu çözmek imkansızdır. SQL'in Nesneye Dayalı bir dil olmadığını kabul etmeliyiz ve çoğaltma kodunun kapsüllenmesi / azaltılması, diğer birçok dilde olduğu gibi SQL'in bir tasarım hedefi değildi.

    Sorgu yeterince uzun / karmaşıksa, sorguyu Satır İçi Tablo Değerli İşlevinde kapsülleyebilirsiniz. Ardından SELECT * FROM dbo.MyTVF(params);"Önizleme" modu için basit bir işlem yapabilir ve "do it" modu için anahtar / değer çiftlerine KATILABİLİRSİNİZ. Örneğin:

    UPDATE tab
    SET    tab.Col2 = tvf.ColB
           ...
    FROM   dbo.Table tab
    INNER JOIN dbo.MyTVF(params) tvf
            ON tvf.ColA = tab.Col1;
  • Bu, belirttiğiniz gibi bir rapor senaryosuysa, ilk raporu çalıştırmak "Önizleme" olur. Birisi raporda gördükleri bir şeyi değiştirmek isterse (belki de bir durum), o zaman ek olarak görüntülenen veriyi değiştirmek beklenir.

    İşlem, bir teklif tutarını belirli bir% veya iş kuralına göre değiştirmekse, bu sunum katmanında (JavaScript?) İşlenebilir.

  • Son kullanıcıya dönük bir işlem için gerçekten bir "Önizleme" yapmanız gerekiyorsa, önce verilerin durumunu yakalamanız gerekir ( UPDATEişlemler için sonuç kümesindeki tüm alanların bir karması veya DELETEişlemleri) ve ardından işlemi gerçekleştirmeden önce, yakalanan durum bilgilerini geçerli bilgilerle karşılaştırın - bu karşılaştırmayı yaptıktan sonra hiçbir şeyin değişmemesi için bir kilit içinde olan bir İşlem içindeHOLD - ve HERHANGİ bir fark varsa, hata ile ROLLBACKdevam edin UPDATEveya yerine devam edin DELETE.

    UPDATEİşlemler için farklılıkları saptamak üzere, ilgili alanlardaki bir karma değerini hesaplamaya bir alternatif, ROWVERSION türünde bir sütun eklemek olacaktır . Bir ROWVERSIONveri tipinin değeri, söz konusu satırda her değişiklik yapıldığında otomatik olarak değişir. Böyle bir sütununuz varsa, SELECTbunu diğer "Önizleme" verileriyle birlikte kullanırsınız ve ardından anahtar değer (ler) ve değer (ler) ile birlikte "emin, devam et ve güncelle" adımına geçirirsiniz değişmek. Ardından, bu karşılaştırmak istiyorsunuz ROWVERSIONgeçti- "Önizleme" den (her anahtar başına) geçerli değerlere sahip değerleri ve sadece devam UPDATEeğer ALLeşleştirilen değerlerden. Buradaki fayda, yanlış negatifler için olası olmasa bile potansiyeli olan bir hash hesaplamanıza gerek kalmaması ve her yaptığınızda biraz zaman almasıdır SELECT. Öte yandan, ROWVERSIONdeğer yalnızca değiştirildiğinde otomatik olarak artırılır, bu yüzden endişelenmeniz gereken hiçbir şey yoktur. Ancak, ROWVERSIONtür 8 bayttır, bu da birçok tablo ve / veya birçok satırla uğraşırken toplanabilir.

    UPDATEOperasyonlarla ilgili tutarsız durumu tespit etmek için bu iki yöntemin her birinin artıları ve eksileri vardır , bu nedenle hangi yöntemin sisteminiz için "con" dan daha fazla "pro" olduğunu belirlemeniz gerekir. Ancak her iki durumda da, Önizleme'nin oluşturulması ile işlemin son kullanıcının beklentilerinin dışında davranışlara neden olması arasındaki gecikmeyi önleyebilirsiniz.

  • Son kullanıcı-karşı karşıya bir "Önizleme" modu yapıyorsanız , kayıtların durumunu belirli bir zamanda yakalamaya, aktarmaya ve değiştirme zamanında kontrol etmeye ek olarak , bir DATETIMEfor SelectTimeve nüfus doldurma aracı GETDATE()ya da benzer bir şey ekleyin . Saklanan Yordam'da kontrol edilebilmesi için bunu saklanan yordama geri döndürülebilmesi için (çoğunlukla tek bir girdi parametresi olarak) uygulama katmanına iletin. Ardından, işlemin "Önizleme" modu değilse, @SelectTimedeğerin geçerli değerinden önce X dakikadan fazla olmaması gerektiğini belirleyebilirsiniz GETDATE(). Belki 2 dakika? 5 dakika? Büyük olasılıkla 10 dakikadan fazla değil. DATEDIFFMINUTES cinsinden bu eşiğin üzerindeyse bir hata atın .


4

En basit yaklaşım genellikle en iyisidir ve gerçekten aynı modülde değil, SQL'de kod çoğaltma ile ilgili bir sorunum yok. Her iki sorgudan sonra farklı şeyler yapıyorlar. Öyleyse neden 'Rota 1' veya Basit Tutun ve saklanan proc'ta iki bölüme sahip değilsiniz, biri yapmanız gereken işi simüle etmek ve diğeri bunu yapmak, örneğin böyle bir şey:

CREATE TABLE dbo.user_table ( rowId INT IDENTITY PRIMARY KEY, a INT NOT NULL, someGuid UNIQUEIDENTIFIER DEFAULT NEWID() );
GO
CREATE PROCEDURE [dbo].[PREVIEW_EXAMPLE2]

    @preview CHAR(1) = 'Y'

AS

    SET NOCOUNT ON

    --!!TODO add error handling

    IF @preview = 'Y'

        -- Simulate INSERT; could be more complex
        SELECT 
            ISNULL( ( SELECT MAX(rowId) FROM dbo.user_table ), 0 ) + 1 AS rowId,
            42 AS a,
            NEWID() AS someGuid

    ELSE

        -- Actually do the INSERT, return inserted values
        INSERT INTO dbo.user_table ( a )
        OUTPUT inserted.rowId, inserted.a, inserted.someGuid
        VALUES ( 42 )

    RETURN

GO

Bu, kendi kendini belgeleme (yani IF ... ELSEtakip edilmesi kolaydır), düşük karmaşıklık (tablo değişkeni yaklaşımı IMO ile kaydetme noktasına kıyasla) olma avantajına sahiptir , bu nedenle hatalara (@Cody'den büyük nokta) sahip olma olasılığı daha düşüktür.

Düşük güven konusundaki görüşünüzle ilgili olarak, anladığımdan emin değilim. Mantıken aynı ölçütlere sahip iki sorgu aynı şeyi yapmalıdır. A UPDATEve a arasında kardinalite uyumsuzluğu olasılığı vardır SELECT, ancak birleşimlerinizin ve ölçütlerinizin bir özelliği olacaktır. Daha fazla açıklayabilir misiniz?

Bir yana, NULL/ NOT NULLözelliğini ve tablolarınızı ve tablo değişkenlerinizi ayarlamanız gerekir, birincil anahtar ayarlamayı düşünün.

Orijinal yaklaşımınız biraz karmaşık görünüyor , INSERT/ UPDATE/ DELETEişlemleri düzden daha yüksek kilitleme seviyeleri gerektirdiğinden muhtemelen kilitlenmelere daha eğilimli olabilir SELECTs.

Gerçek dünya süreçlerinizin daha karmaşık olduğundan şüpheleniyorum, bu nedenle yukarıdaki yaklaşımın onlar için işe yaramayacağını düşünüyorsanız, daha fazla örnekle geri gönderin.


3

Endişelerim aşağıdaki gibidir.

  • İşlem işleme, Başlatmaya Başla / Başlat Yakala bloğunda iç içe yerleştirme standart modeline uymaz. Bu bir şablonsa, birkaç adım daha saklanan bir yordamda, veriler değiştirilmiş olarak önizleme modunda bu işlemden çıkabilirsiniz.

  • Biçimi izlemek geliştirici çalışmasını artırır. İç sütunları değiştirirlerse, tablo değişkeni tanımını da değiştirmeli, sonra geçici tablo tanımını değiştirmeli, sonra sondaki ekleme sütunlarını değiştirmelidirler. Popüler olmayacak.

  • Bazı saklı yordamlar her seferinde aynı veri biçimini döndürmez; sp_WhoIsActive'ı yaygın bir örnek olarak düşünün.

Bunu yapmak için daha iyi bir yol sunmadım ama sahip olduğun şeyin iyi bir model olduğunu düşünmüyorum.

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.