Optimistic Lock Nedir ? Pessimistic Lock Nedir ? Data concurrency

Db'si olan ve son kullanıcı tarafından CRUD işlemlerinin bolca yapılabildiği bir proje geliştiriyorsanız veri tutarlılığı sizin için oldukça önemli bir hal almak durumundadır. Kayıtlı olan veriyi son kullanıcıya ulaştırabilip en güncel veri üzerinden transaction'ları işleyebilmek ve stale yada dirty data olarak da adlandırılan bayat veriyi handle edip kullanıcının erişmesini engellemek oldukça önemlidir. 

Transactional operasyonlar Concurrency'yi sağlayabilmek adına genelde üzerinde işlem yapılan veriye lock işlemi uygulanarak gerçekleştirilirler. Bu lock işlemi için 2 farklı yaklaşım vardır. Pessimistik Lock ve Optimistic Lock.

Pessimistic Lock

O an işlem gerçekleşirken üzerinde çalışılan kayıt lock'lanır ki o anda başka birisi o kayıt üzerinde değişiklik yapmasın. Bu işlem session bazlı olur ve transaction başlarken açılan session sonlandırılıncaya veya rollback yapılıncaya kadar işlem yapılan row db de lock'lanır. Örnek olarak bir bankacılık uygulaması düşünün ve bir hesaba aynı anda hem para çekme hemde para yatırma işlemi geldi. İlk para yatırma işlemini yapan thread öncelikli düşündüğümüzde bu transaction'ı gerçekleştiren session o account'u işlem sonlanıncaya kadar lock'lar ve para çekme işlemini bekletir. Transaction sonlandıktan sonra diğer thread'in gerçekleştireceği para çekme işleminin session'nını açarak güncel veri üzerinden işlemlerin gerçekleşmesine olanak sağlar. Bunu yapmasındaki amaç güncel veri üzerinden transaction'ı geçirip oluşabilecek kayıpları engellemektir. Ancak pessimistic lock'ın deadlock'lara sebep olabileceğini de unutmayalım.

 

Optimistic Lock

Optimistic Lock ise, adından da anlaşılacağı üzre "iyimser" birden fazla işlemin birbirini etkilemeden gerçekleşeceğini ve kimsenin kimse üzerinde bir lock koymayacağını söyler. Diğer bir deyişle farklı thread'ler de aynı row üzerinde işlem yapılırken herhangi bir lock işlemi olmadan update edilmek istenen verinin bayat olup olmadığını o verinin kayıtlı olduğu tabloda yer alan versiyon numarası olarak da adlandırılan bir column'da bulunan değeri kontrol eder ve eğer versiyon eşleşmiyorsa yani veri bayat ise işlem geri çekilir.

Peki bayat(stale) data ne demek ?

Örnek üzerinden anlatacak olursak; bir internet sitesinde kayıtlı bulunan adres bilginizi güncellemek istiyorsunuz. Aynı anda 2 farklı bilgisayardan bilgileri güncelle sayfasını açtınız ve adresiniz o an "Samsun" olarak kayıtlı yani 2 ekranda da "Samsun" yazıyor. İlk bilgisayarda bulunan kişi adres bilgisini "Ankara" olarak değiştirdi ve güncelle butonuna basıp bilgiyi güncelledi.

İkinci ekranda bulunan kişi ise ekranda halen "Samsun" yazılı iken adres bilgisini "İstanbul" olarak değiştirdi ve güncelle butonuna basıp bilgiyi güncelledi. Ekranda yazan "Samsun" kaydı artık bizim için bayat bir kayıttır ve birinci kullanıcı değişikliği "Samsun" => "Ankara" yaptığını düşünürken ikinci kişi bu değişikliği "Samsun" => "İstanbul" yaptığını düşünüyor. Halbuki gerçekte olan ikinci kişi adres bilgisi ekranda "Ankara" iken => "İstanbul" olarak değiştirmiş oldu.

Ne oldu ? Pek de istemediğimiz bir case oluştu. İkinci kullanıcı Samsun olan kaydı İstanbul yaptığını düşünürken aslında Ankara olan kaydı İstanbul yaptı. Yani stale olan kaydı güncellemiş oldu.

Optimistic Lock ile ikinci kullanıcının stale olan veriyi update etmesine şu şekilde engel olabiliriz; Eğer Ado.Net kullanıyorsanız ve db de bulunan her bir row icin birer versiyon

numarasi vb gibi kaydinizvar ise query nizde where koşuluna o row için güncel olarak bulunan version bilgisini ekleyerek kontrol sağlayabiliriz veya Entity Framework yada NHibernate gibi ORM tool'larından birini kullanıyorsanız bu işlemi size bırakmadan güncellenmek istenen row'a ait versiyon numarasını select işleminde memory de tutuyor ve o veri için update transaction'ı execute edilirken bu versiyon numarası db de karşılaştırıyor. Eğer o versiyon numarası db de bulunan ile aynı ise versiyon numarasını 1 artırıp execution'a izin veriyor değilse hata fırlatıyor. Hata mesajı olarak kullanılan ORM türüne göre "The record you attempted to edit was modified by another user after you got the original value" gibi bir message return ediyor.

 

Hem optimistic hemde pessimistic lock konuları çok fazla önemsemediğimiz anlar olsa da oldukça önemli konulardır. Sonraki yazılarımızda Nhibernate veya Entity Framework kullanarak nasıl bir Optimistic Lock yapısı implement edebiliriz inceleyeceğiz.

Fluent NHibernate Nedir ve Kullanımı

ORM Nedir ?

ORM (object relational mappers) uygulamalarınızda CRUD (Create, Read, Update, and Delete) işlemleri için basit bir şekilde data'ya erişimlerimizi sağlayan yapılardır. Çeşitli ORM framework'leri uzun zamandan beri data-model arasındaki veri alış verişini sağlamak için kullanılmakta ve ORM ler sql scriptleri execute etmeden CRUD işlemlerini doğrudan yapabilmemiz için kod yazmamıza olanak sağlarlar. Özetle, ORM kullanarak uygulamalarımızdaki data-model ilişkisini(database modellemesini) object-model ilişkisine dönüştürebiliriz.

Neden Fluent NHibernate ?

Klasık NHinernate, database de bulunan her bir tablo için class mapping bilgilerini .hbm uzantılı  XML formatındaki dosyalarda saklıyordu ve aslına bakılırsa biraz zahmetli bir işlemdi. Fluent NHibernate'de ise artık .hbm uzantılı mapping bilgilerini içeren XML formatındaki dosyalar kaldırıldı ve bu işlem code-behind'a taşındı ve diğer ORM'lere göre daha hızlı olduğu kabul edilir.

Kısaca özelliklerini sıralamak gerekirse;

  • Easy data-access and data-mapping,
  • Compile-time name- and type-safety,
  • IntelliSense to show you which fluent methods are available at any point,
  • Automapper impl.

Fluent NHibernate ile kolayca Linq sorguları yapabilir ve böylelikle Fluent bir Api oluşturmada kullanabiliriz. Şimdi ise projemize nasıl Fluent NHibernate entegre edip geliştirme yapabiliriz bunu incelicez.

Fluent NHibernate Kurulum

İlk olarak Visual Studio üzerinde yeni bir Console Application projesi açalım. Daha sonra Nuget üzerinden Fluent NHibernate referanslarını projemize indirip kuralım. Install-Package FluentNHibernate

Database Tablo Oluşturulması

Fluent NHibernate kullanabilmek için öncelikle bir database'e ihtiyacımız var. SQL üzerinde FNH_Db adında adında bir database'imiz olsun ve bu db'ye ait aşağıda bulunduğu gibi User adında bir tablo oluşturalım.

CREATE TABLE dbo.Users  
(
    Id int PRIMARY KEY NOT NULL,  
    Name varchar(25) NOT NULL,  
    SurName varchar(25) NOT NULL
)  

User Tablosuna Karşılık Gelen User.cs Oluşturulması

Yukarıda tanımlamış olduğumuz User tablosuna karşılık gelen User.cs aşağıdaki gibi oluşturuyoruz.

public class User
   {
       public virtual int Id { get; set; }
       public virtual string Name { get; set; }
       public virtual string SurName { get; set; }
   }

Mapping Class'ının Oluşturulması

Database tablomuz ve buna karşılık gelen class'ımız hazır ancak halen User objesinde bulunan hangi alan tabloda bulunan hangi alana karşılık geliyor bunu belirtmedik. Bunun için Fluent NHibernate'e ait olan ClassMap adında bir class bulunmakta ve gerekli mapping işlemlerini yapacağımız class ClassMap<T>'den inherit olmuş şekilde oluşturulacak. Burda ki T bizim User class'ımız oluyor.

UserMap.cs adındaki mapping class'ı aşağıdaki gibi olacak şekilde tanımlıyoruz.

   public class UserMap : ClassMap<User>
   {
       public UserMap()
       {
           Id(x => x.Id);
           Map(x => x.Name);
           Map(x => x.SurName);
           Table("User");
       }
   }

DB Connection Sağlanması

Şimdiki işlem ise Fluent Nhibernate kullanarak yukarıda tanımlamış olduğumuz FNH_Db isimli database'e bağlanmak. Bunun için OpenSession adında bir metot oluşturalım.

       public static ISession OpenSession()
       {
           string connectionString = "FNH_Db conn string";
           var sessionFactory = Fluently.Configure()
               .Database(MsSqlConfiguration.MsSql2012
                .ConnectionString(connectionString).ShowSql()
               )
               .Mappings(m =>m.FluentMappings.AddFromAssemblyOf<User>())
               .ExposeConfiguration(cfg => new SchemaExport(cfg)
               .Create(false, false))
               .BuildSessionFactory();
           return sessionFactory.OpenSession();
       }

Tablo'da Bulunan Verileri Alma

Aşağıda yer alan kod bloğu ise db de bulunan tablodaki verileri query etmemizi sağlar.

using (var session = OpenSession())
     {
          var users = session.Query<User>().ToList();
     }

Yukarıda bulunan geliştirmeleri yaptığınızda db bulunan bir proje geliştirmede basitçe connection sağlayarak data-model ilişkisini kurabilirsiniz.  

Not: Yazıyı basit tutmak adına herhangi bir design pattern den bahsetmedim ancak Db olan yerde Crud işlemleri vardır, Crud olan yerde Ado.Net veya ORM vardır, ORM varsa Fluent NHibernate vardır ve Fluent NHibernate de Repository Design Pattern ile birlikte fıstıklı baklava gibi gider :) 

Fluent NHibernate ile ilgili daha detaylı bilgi almak ve geliştirmeleri takip edebilmek için bu linke göz atabilirsiniz.

OraOledb Not enough storage is available to complete this operation.

There are like 100 questions and answers about this problem but none of them did solve my problem.

What I was was trying to do is getting blob data from oracle database using ado.net just like below code,

OleDbParameter returnValue = new OleDbParameter("v_PictureBlodData", OleDbType.LongVarBinary);


and when I try to execute the store procedure & query I was getting an error which sais "Not enough storage is available to complete this operation."

Most of the comments for this problem is about, server Ram capacity or Storage capacity. But I was really sure that my server had enough Ram and Storage capacity.

 

So, what solved my problem is just giving a size value for OleDbParameter which is 4000000

OleDbParameter returnValue = new OleDbParameter("v_PictureBlodData", OleDbType.LongVarBinary, 4000000);


If you guys are sure about your server capacity, you can check it by trying to give a size value for the parameter.

Store Procedure if / else Kullanımı

Store Procedure'ler veri tabanı kullanılan projelerin olmazsa olmazıdır.Bunun en önemli nedeni Güvenlik ve Hız dır. Ado.net kullanılanılarak veri insert, update, delete işlemleri yapılan bir uygulamada Store Procedure değilde direkt olarak SqlCommand nesnesine "insert,update,delete" gibi query'ler yazmak pek önerilen bir yol değildir. Nedeni de yine az önce söylediğim gibi öncelikle hız, güvenliktir ve yazdığın kodun ne kadar profesyonelce yazıldığıyla ilişkilidir.

Store Procedure'lerde IF ELSE kullanabildiğiniz vakit emin olun uygulama tarafında yazdığınız C# kodları %30'lara kadar azalacaktır. Örnek olarak verecek olursak; database'de kayıtlı Users adında bir tablom var ve bu tablo Ad, Soyad, Email bilgilerini alarak kullanıcıları kaydediyor olsun. Store Procedure kullanarak bu kullanıcıları database'e kaydeden bir uygulama geliştirecek olduğumuzda, UserInsert ve  UserUpdate işlemlerini yapan 2 adet Store Procedure ihtiyacımız var diyelim. Aslında IF ELSE kullanarak bunu bir Store Procedure ile yapmak hem daha hızlı, hemde C# tarafında bizi çok daha fazla kod yazmaktan kurtarıyor.

Users Tablosu Örneği;

CREATE PROCEDURE InsertUser   //Procedure İsmi

    @Ad varchar(20),   //aldığı atribüt'ler

    @Soyad varchar(20),

    @Email varchar(20)

AS

BEGIN

      DECLARE@HasExist int  //Email adresine göre o kullanıcı daha önce kayıt olmuş mu diye kontol etmemizi sağlayacak olan değişken

      Select @HasExist COUNT(1)

      from Users

      Where Email=@Email   //Bu email adresi kayıtlı mı diye bakıyoruz

IF(@HasExist=0) //Üstteki sorgudan dönen değer @HsExist değişkenine atanıyor ve eğer o kayıt var ise "0" dan farklı bir değer yok ise "0" değerini dönüyor, "0" döndüğünde yeni bir kayıt ekleyeceğimizi anlıyoruz ve aşağıda olduğu gibi Procedure'e parametre olarak gelen değerleri alıp tabloya yeni bir User ekliyoruz

BEGIN

      Insert Into Users   //Insert işlemi

      values

            (@Ad,@Soyad,@Email)

END

ELSE  //Eğer @HasExist "0" a eşit değilse bu demek oluyorki bu kullanıcı database'de kayıtlı bulunmakta, bu seferde Procedure'e parametre olarak gelen değerleri alarak o kullanıcının bilgilerini aşağıda olduğu gibi Update ediyoruz

BEGIN

      Update Users   //Update işlemi

      set

            Ad =@ Ad,  //bilgileri update ediyoruz

            Soyad=@Soyad,

            Email=@Email

      WHERE Email=@Email  //where koşulu O kullanıcıyı bulmamızı sağlıyor

END

END

 

 

Northwind Database Örnek Soru Cevapları

Northwind Database Örnek SQL Sorgu ve Cevapları

1-)Tüm cirom ne kadar?

SELECT SUM((UnitPrice*Quantity)-(UnitPrice*Quantity*Discount))

ROM[Order Details]

2-)Bugün doğumgünü olan çalışanlarım kimler?

select *

from employees

where month(BirthDate)=month(getdate()) and day(BirthDate)=day(getdate())

3-)Hangi çalışanım hangi çalışanıma bağlı? (İsim - İsim)

select e.FirstName +e.LastName,em.FirstName +em.LastName

from Employees e

inner join Employees em

on e.EmployeeID=em.EmployeeID

4-)Çalışanlarım ne kadarlık satış yapmışlar? (Gelir bazında)

select e.FirstName + ' ' + e.LastName, SUM(Quantity*UnitPrice)

from Employees e

inner join Orders o

on e.EmployeeID=o.EmployeeID

inner join [Order Details] od

on o.OrderID=od.OrderID

group by e.FirstName,e.LastName

5-)Hangi ülkelere ihracat yapıyorum?

select distinct country

from customers

6-)Ürünlere göre satışım nasıl? (Ürün-Adet-Gelir)

select p.ProductName,SUM(od.Quantity) as Adet,sum(od.Quantity*od.UnitPrice*(1-od.Discount)) as Gelir

from Products p

inner join [Order Details] od

on p.ProductID=od.ProductID

group by p.ProductName

order by 3 desc

7-)Ürün kategorilerine göre satışlarım nasıl? (Gelir bazında)

select c.CategoryName,SUM(od.Quantity*od.UnitPrice) as Gelir

from Categories c

inner join Products p

on c.CategoryID=p.CategoryID

inner join [Order Details] od

on p.ProductID =od.ProductID

group by c.CategoryName

order by 2 desc

8-)Ürün kategorilerine göre satışlarım nasıl? (Adet bazında)

select c.CategoryName,SUM(od.Quantity) as Adet

from Categories c

inner join Products p

on c.CategoryID=p.CategoryID

inner join [Order Details] od

on p.ProductID =od.ProductID

group by c.CategoryName

order by 2 desc

9-)Çalışanlarım ürün bazında ne kadarlık satış yapmışlar? (Çalışan  –  Ürün – Adet – Gelir)

select (e.FirstName + e.LastName) as Çalisanp.ProductNamesum(od.Quantity), SUM(od.Quantity*od.UnitPrice)

from Products p

inner join [Order Details] od

on p.ProductID=od.ProductID

inner join Orders o

on od.OrderID=o.OrderID

inner join Employees e

on o.EmployeeID=e.EmployeeID

group by e.FirstName + e.LastName,p.ProductName

order by 4 desc

10-)Hangi kargo şirketine toplam ne kadar ödeme yapmışım?

select s.CompanyName,SUM(o.Freight) as [Ödenen Ücret]

from Shippers s

inner join Orders o

on s.ShipperID=o.ShipVia

group by s.CompanyName

11-)Tost yapmayı seven çalışanım hangisi? (Basit bir like sorgusu J)

select FirstName + ' ' +LastName

from Employees

where Notes like'%Toast%'

12-)Hangi tedarkçiden aldığım ürünlerden ne kadar satmışım? (Satış bilgisi order details tablosundan alınacak) (Gelir ve adet bazında)

select s.CompanyName,p.ProductName, SUM(od.Quantity) as adet,sum(od.Quantity*od.UnitPrice) as gelir

from Suppliers s

inner join Products p

on s.SupplierID=p.SupplierID

inner join [Order Details] od

on p.ProductID=od.ProductID

group by  s.CompanyName , p.ProductName

order by 3 desc

13-)En değerli müşterim hangisi? (en fazla satış yaptığım müşteri) (Gelir ve adet bazında)

select top 1 c.CompanyName,SUM(od.Quantity) as adet,SUM(OD.Quantity*od.UnitPrice) as gelir

from Customers c

inner join orders o

on c.CustomerID=o.CustomerID

inner join [Order Details] od

on o.OrderID=od.OrderID

group by c.CompanyName

order by 3 desc

14-)Hangi ülkelere ne kadarlık satış yapmışım?

select c.Country,SUM(od.Quantity*od.UnitPrice)

from Customers c

inner join Orders o

on c.CustomerID=o.CustomerID

inner join [Order Details] od

on o.OrderID=od.OrderID

group by c.Country

order by 2 desc

15-)Zamanında teslim edemediğim siparişlerim ID’leri  nelerdir ve kaç gün geç göndermişim?

select OrderID,datediff(DAY,ShippedDate,RequiredDate) as dt

from Orders

where datediff(DAY,ShippedDate,RequiredDate)>0

16-)Ortalama satış miktarının üzerine çıkan satışlarım hangisi?(subquerry kullandık, querrynin içinde querry tanımladık)

select *

from [Order Details] od

where Quantity>(select AVG(Quantity) from [Order Details])

order by od.Quantity desc

17-)Satışlarımı kaç günde teslim etmişim?

select OrderID,datediff(DAY,OrderDate,ShippedDate)

from Orders

where ShippedDate not like 'NULL'

order by 2 desc