Düzenlenebilir bir listeyi bir veritabanında saklamak


54

Kullanıcıların çeşitli istek listelerine öğe ekleyebileceği bir dilek listesi sistemi üzerinde çalışıyorum ve kullanıcıların öğeleri daha sonra yeniden sipariş etmesine izin vermeyi planlıyorum. Hızlı kalırken ve bir karışıklığa dönüşmüyorken bunu bir veritabanında saklamaya devam etmenin en iyi yolundan gerçekten emin değilim (bu uygulama oldukça büyük bir kullanıcı tabanı tarafından kullanılacak, bu yüzden aşağı inmesini istemiyorum. eşyaları temizlemek için).

Başlangıçta bir positionsütun denedim , ancak etrafta dolaştığınızda diğer her bir öğenin pozisyon değerini değiştirmek zorunda kalmanın yetersiz kaldığı anlaşılıyor.

Önceki (veya sonraki) değere atıfta bulunmak için kendi referanslarını kullanan insanlar gördüm, ancak yine de listedeki birçok öğeyi güncellemeniz gerekecek gibi görünüyor.

Gördüğüm bir diğer çözüm, ondalık sayıları kullanmak ve aralarındaki boşluklara öğeleri yapıştırmak, ki şu ana kadarki en iyi çözüm gibi görünüyor, ama daha iyi bir yol olması gerektiğinden eminim.

Tipik bir listenin yaklaşık 20 kadar eşya içerdiğini söylerdim ve muhtemelen 50 ile sınırlayacağım. Yeniden sıralama sürükle ve bırak yöntemini kullanır ve muhtemelen yarış koşullarını ve bunun gibi engelleri önlemek için gruplar halinde yapılacaktır. ajax istekleri. Eğer önemliyse postgres kullanıyorum (heroku'da).

Bir fikri olan var mı?

Herhangi bir yardım için şerefe!


Biraz kıyaslama yapar ve bize IO veya Veritabanının bir darboğaz olup olmayacağını söyler misiniz?
rwong

Stackoverflow ilgili soru .
Jordão

Kendine referansla, bir öğeyi listedeki bir yerden diğerine taşırken yalnızca 2 öğeyi güncellemeniz gerekir. Bkz. En.wikipedia.org/wiki/Linked_list
Pieter B

Hmm, bağlı listelerin neden cevaplarda pek dikkat çekmeye başladığından emin değilsiniz.
Christiaan Westerbeek

Yanıtlar:


32

İlk olarak, ondalık sayılarla akıllıca bir şey yapmayı denemeyin, çünkü sizi kızdırırlar. REALve DOUBLE PRECISIONkusursuzdur ve onlara ne koyduğunuzu doğru bir şekilde göstermeyebilir. NUMERICkesindir, ancak doğru hamle dizisi sizi hassasiyetten mahveder ve uygulamanız kötü bir şekilde bozulur.

Hareketleri tek iniş ve çıkışlarla sınırlamak tüm işlemi çok kolaylaştırır. Sıralı olarak numaralandırılmış öğelerin bir listesi için, konumunu azaltarak ve önceki azalışta ne olursa olsun, pozisyon numarasını artırarak bir öğeyi yukarı taşıyabilirsiniz. (Başka bir deyişle, madde 5olur 4ve ne 4olur 5, etkin bir şekilde Moron'un cevabında tarif edildiği gibi değiş tokuş olur .) Aşağı doğru ilerletmek tam tersi olur. Bir listeyi ve konumu benzersiz bir şekilde tanımlayana göre tablonuzu indeksleyin ve UPDATEçok hızlı bir şekilde çalışacak bir işlemin içinde iki saniye ile yapabilirsiniz . Kullanıcılarınız listelerini insanüstü hızlarda yeniden düzenlemiyorsa, bu fazla bir yüke yol açmayacak.

Sürükle ve bırak hamle (örneğin öğeyi taşımak 6öğeler arasında oturmak 9ve 10) küçük bir ustalık ve yeni pozisyon eskisinin altında veya üstünde olmasına bağlı olarak farklı şekilde yapılması gerekiyor. Yukarıdaki örnekte, tüm konumları daha büyük artırarak 9, öğenin 6konumunu yeni olacak şekilde güncelleyerek 10ve sonra 6boşaltılan noktayı doldurmaktan daha büyük olan her şeyin konumunu azaltarak bir delik açmanız gerekir . Daha önce tarif ettiğim aynı endekslemeyle, bu hızlı olacak. Bunu, işlemin dokunduğu satır sayısını en aza indirerek tanımladığımdan biraz daha hızlı hale getirebilirsiniz, ancak bu bir darboğaz olduğunu kanıtlayana kadar ihtiyacınız olmayan bir mikrooptimizasyondur.

Her iki durumda da, veritabanını ev yapımı, çok fazla akıllıca bir çözümle gerçekleştirmeyi denemek genellikle başarıya yol açmaz. Tuzlarına değer veri tabanları, bu işlemleri çok, çok iyi insanlar tarafından çok, çok hızlı bir şekilde yapmak için dikkatlice yazılmıştır.


Aynen böyle bir yıl önce bir milyarlarca yıl önce sahip olduğumuz bir proje teklif hazırlama sisteminde nasıl kullandım. Access'te bile güncelleme hızlı bir şekilde bölündü.
HLGEM

Açıklama için teşekkürler, Blrfl! İkinci seçeneği yapmaya çalıştım, ancak listenin ortasındaki öğeleri silersem, konumlarda boşluklar bırakacağını öğrendim (oldukça naif bir uygulama oldu). Bu gibi boşluklar oluşturmaktan kaçınmanın kolay bir yolu var mı, yoksa bir şeyi yeniden sipariş ettiğimde her zaman elle yapmam gerekir mi?
Tom Brunoli

2
@TomBrunoli: Kesin olarak söylemeden önce uygulama hakkında biraz düşünmek zorunda kalacağım, ancak otomatik olarak yeniden numaralandırmanın çoğunu veya tamamını otomatik olarak tetikleyicilerle çekebilirsiniz. Örneğin, 7. maddeyi silerseniz, tetikleyici silme gerçekleştikten sonra 7'den büyük olan aynı listedeki tüm satırları azaltır. Uçlar aynı şeyi yapar (bir öğe 7 eklemek, tüm satırları 7 veya daha fazla artıracaktır). Bir güncelleme için tetikleyici (örneğin, madde 3'ü 9 ile 10 arasında hareket ettir) orta derecede daha karmaşık olacaktır, ancak kesinlikle yapılabilir alanın içindedir.
Blrfl

Aslında daha önce tetikçilere bakmamıştım ama bu bunu yapmanın iyi bir yolu gibi görünüyor.
Tom Brunoli

1
@ TomBrunoli: Bunu yapmak için tetikleyicileri kullanmanın kaskadlara neden olabileceği aklıma geldi. Bir işlemdeki tüm değişikliklerin saklandığı prosedürler bunun için daha iyi bir yol olabilir.
Blrfl

15

Buradan da aynı cevap https://stackoverflow.com/a/49956113/10608


Çözüm: indexbir dize yapın (çünkü dizeler, özünde sonsuz "keyfi kesinlik" e sahiptir). Veya bir int indexkullanıyorsanız, 1 yerine 100 artırın.

Performans sorunu şudur: iki sıralanan öğe arasında "arasında" değer yoktur.

item      index
-----------------
gizmo     1
              <<------ Oh no! no room between 1 and 2.
                       This requires incrementing _every_ item after it
gadget    2
gear      3
toolkit   4
box       5

Bunun yerine, bunu yapın (aşağıda daha iyi bir çözüm):

item      index
-----------------
gizmo     100
              <<------ Sweet :). I can re-order 99 (!) items here
                       without having to change anything else
gadget    200
gear      300
toolkit   400
box       500

Daha da iyisi: işte Jira bu problemi nasıl çözüyor. Onların "sıralaması" (indeks dediğiniz şey), sıralanan öğeler arasında bir ton solunum odasına izin veren bir dize değeridir.

İşte birlikte çalıştığım jira veritabanının gerçek bir örneği

   id    | jira_rank
---------+------------
 AP-2405 | 0|hzztxk:
 ES-213  | 0|hzztxs:
 AP-2660 | 0|hzztzc:
 AP-2688 | 0|hzztzk:
 AP-2643 | 0|hzztzs:
 AP-2208 | 0|hzztzw:
 AP-2700 | 0|hzztzy:
 AP-2702 | 0|hzztzz:
 AP-2411 | 0|hzztzz:i
 AP-2440 | 0|hzztzz:r

Bu örneğe dikkat edin hzztzz:i. Bir dizi sırasının avantajı, iki öğe arasında odadan çıkmanız, yine de başka bir şeyi yeniden sıralamanıza gerek kalmamasıdır. Odağı daraltmak için dizeye daha fazla karakter eklemeye başlarsınız.


1
Sadece tek bir kaydı güncelleyerek bunu yapmanın bir yolunu bulmaya çalışıyordum ve bu cevap kafamda çok iyi düşündüğüm çözümü açıklıyor.
NSjonas

13

Önceki (veya sonraki) değere atıfta bulunmak için kendi referanslarını kullanan insanlar gördüm, ancak yine de listedeki birçok öğeyi güncellemeniz gerekecek gibi görünüyor.

Neden? Bağlantılı bir liste tablosu yaklaşımını sütunlarla (listID, itemID, nextItemID) aldığınızı varsayalım.

Listeye yeni bir öğe eklemek bir ekleme ve bir değiştirilmiş satır tutar.

Bir öğenin yeniden konumlandırılması üç satırda değişiklik yapılmasına neden olur (taşınmakta olan öğe, önceki öğe ve yeni konumundan önceki öğe).

Bir öğenin kaldırılması bir silmeye ve bir değiştirilmiş satıra mal olur.

Bu maliyetler, listenin 10 veya 10.000 maddeye sahip olmasına bakılmaksızın aynı kalır. Her üç durumda da, hedef satır ilk liste öğesi ise, daha az değişiklik yapılması gerekir. Son liste öğesinde daha sık çalışıyorsanız, bir sonraki yerine prevItemID'yi saklamak faydalı olabilir.


10

“ama bu oldukça verimsiz görünüyor”

Eğer mı ölçmek o? Yoksa bu sadece bir tahmin mi? Bu tür varsayımları kanıt olmadan yapmayın.

"Liste başına 20 ila 50 ürün"

Dürüst olmak gerekirse, bu "çok fazla öğe" değil, bana çok az geliyor.

"Konum sütunu" yaklaşımına sadık kalmanızı öneririm (eğer sizin için en basit uygulama ise). Böyle küçük liste boyutları için, gerçek performans sorunlarını yaşamadan önce gereksiz optimizasyona başlamayın


6

Bu gerçekten bir ölçek meselesi ve kullanım örneği ..

Bir listede kaç tane ürün bekliyorsunuz? Milyonlarca, bence ondalık yolun bariz olanıdır.

6 ise tamsayıların numaralandırılması açık bir seçimdir. s Ayrıca sorular listelerin nasıl düzenlendiği veya yeniden düzenlendiğidir. Yukarı ve aşağı oklar kullanıyorsanız (bir seferde bir yuva yukarı veya aşağı hareket ediyor), i tam sayıları kullanır, sonra hareket halindeyken prev (veya sonraki) ile yer değiştirir.

Ayrıca, ne sıklıkta iş yaparsanız, kullanıcı 250 değişiklik yapabilirse, o zaman bir kerede işleme koyarsanız, yeniden numaralandırma ile tamsayıları tekrar ediyorum ...

tl; dr: Daha fazla bilgiye ihtiyacınız var.


Düzenleme: "İstek listeleri" birçok küçük listeye benziyor (varsayım, bu yanlış olabilir). (Her listenin kendi pozisyonu vardır)


Soruyu biraz daha bağlamda güncelleyeceğim
Tom Brunoli

kesinlikler sınırlı olduğundan ve her eklenen öğe potansiyel olarak 1 bit sürdüğü için ondalık sayılar çalışmıyor
njzk2

3

Amaç, yeniden sipariş işlemine göre veritabanı işlemlerinin sayısını en aza indirmektir:

Farz et

  • Tüm alışveriş ürünleri 32-bit tamsayılarla sayılabilir.
  • Bir kullanıcının istek listesi için bir maksimum boyut sınırı vardır. (Bazı popüler web sitelerinin limit olarak 20 - 40 öğe kullandığını gördüm)

Kullanıcının sıralanmış istek listesini, bir sütunda dolu bir tamsayı dizisi (tamsayı dizileri) olarak saklayın. Dilek listesi her yeniden sıralandığında, dizinin tamamı (tek satır; tek sütun) güncellenir - tek bir SQL güncellemesiyle gerçekleştirilir.

https://www.postgresql.org/docs/current/static/arrays.html


Amaç farklı ise, "pozisyon sütun" yaklaşımı ile sopa.


"Hız" ile ilgili olarak, saklı yordam yaklaşımını karşılaştırdığınızdan emin olun. Bir dilek listesi karışıklığı için 20'den fazla ayrı güncelleme yayınlamak yavaş olabilirken, saklı yordamın kullanılmasının hızlı bir yolu olabilir.


3

Tamam, son zamanlarda bu zor problemle karşı karşıyayım ve bu soru ve cevap yazısındaki tüm cevaplar birçok ilham verdi. Gördüğüm şekilde, her çözümün avantajları ve dezavantajları var.

  • Eğer positionalan boşluklar olmadan sıralı olmak zorunda, o zaman temelde tüm listemi yeniden gerekecektir. Bu bir O (N) işlemidir. Avantaj, müşteri tarafının siparişi almak için herhangi bir özel mantığa ihtiyaç duymamasıdır.

  • Eğer O (N) işleminden kaçınmak istiyorsak, BUT STILL kesin bir sekans tutuyorsa, yaklaşımlardan biri "önceki (veya sonraki) değere atıfta bulunmak için kendi kendine referans" kullanmaktır. Bu, ders kitabı bağlantılı liste senaryosudur. Tasarım gereği "listedeki bir sürü başka eşya" oluşmayacaktır. Bununla birlikte, bu, siparişi türetmek için bağlantılı liste travma mantığını uygulamak için müşteri tarafını (bir web servisi veya belki bir mobil uygulama) gerektirir.

  • Bazı varyasyonlar referans, yani bağlantılı liste kullanmaz. Tüm sırayı, bir JSON dizisinde bir dizi gibi bağımsız bir blob olarak temsil etmeyi seçtiler [5,2,1,3,...]; Böyle bir düzen daha sonra ayrı bir yerde saklanır. Bu yaklaşım aynı zamanda müşteri taraf kodunun bu ayrı sipariş bloğunu korumasını gerektiren bir yan etkiye sahiptir.

  • Çoğu durumda, kesin sırayı kaydetmemize gerek yoktur, sadece her kayıt arasında göreceli bir sıra tutmamız gerekir. Bu nedenle sıralı kayıtlar arasındaki boşluklara izin verebiliriz. Varyasyonlar şunları içerir: (1) 100, 200, 300 ... gibi boşluklarla tamsayı kullanmak, ancak hızla boşlukları tükenir ve ardından kurtarma işlemine ihtiyaç duyarsınız; (2) doğal boşluklarla birlikte gelen ondalık sayısını kullanmak, ancak nihai kesin sınırlama ile yaşayıp yaşamayacağınıza karar vermeniz gerekir; (3) bu cevapta açıklandığı gibi string-tabanlı sıralamayı kullanmak ancak zor uygulama tuzaklarına dikkat etmek .

  • Asıl cevap "bağlıdır" olabilir. İş gereksiniminizi tekrar ziyaret edin. Örneğin, eğer bir dilek listesi sistemi ise, şahsen mutlu bir şekilde, "sahip olması gereken", "olması gereken", "belki daha sonra" olarak adlandırılan ve daha sonra belirli olmayan öğeleri sunan bir sistem organizasyonu kullanırdım. Her rütbe içinde sipariş. Eğer bir dağıtım sistemi ise, teslimat süresini doğal boşlukla birlikte gelen kaba bir sıra olarak çok iyi kullanabilirsiniz (ve aynı anda teslimat yapılmayacağından doğal çatışma önleme). Kilometreniz değişebilir.


2

Konum sütunu için kayan nokta sayısı kullanın.

Daha sonra "taşındı" satırındaki yalnızca konum sütununu değiştirerek listeyi yeniden sıralayabilirsiniz.

Temel olarak, kullanıcı "mavi" den sonra "sarı" den önce "kırmızı" konumlandırmak istiyorsa

O zaman hesaplaman gerekiyor.

red.position = ((yellow.position - blue.position) / 2) + blue.position

Birkaç milyon yeniden konumlandırmadan sonra, kayan nokta sayıları o kadar küçük olabilir ki, "arasında" yoktur - ancak bu bir tek boynuzlu atı denemek kadar muhtemeldir.

Bunu, 1000 di ilk boşluğu olan bir tamsayı alanı kullanarak uygulayabilirsiniz. Böylece intial oringringiniz 1000-> mavi, 2000-> Sarı, 3000-> Kırmızı olur. Mavi hareket ettikten sonra maviden sonra 1000-> mavi, 1500-> Kırmızı, 2000-> Sarı olurdu.

Sorun şu ki, 1000 gibi büyük bir boşluk ile 10 hamle kadar az hareketle sizi 1000-> mavi, 1001-pon, 1004-> biege ...... gibi bir duruma sokacaksınız - artık Tüm listeyi yeniden numaralandırmadan "mavi" den sonra bir şey eklemek için. Kayan noktalı sayıları kullanarak, her zaman iki konum arasında "yarım" bir nokta olacaktır.


4
Yüzenlere göre bir veri tabanında dizine alma ve sıralama, inçlerden daha pahalıdır . Ints aynı zamanda hoş bir sıra türüdür ... istemcide sıralanabilmesi için bit olarak gönderilmesi gerekmez (yazdırıldığında aynı kılan ancak farklı bit değerlerine sahip iki sayı arasındaki fark).

Ancak, ints kullanan herhangi bir şema, sipariş her değiştiğinde listedeki satırların tümünü / çoğunu güncellemeniz gerektiği anlamına gelir. Yüzenleri kullanarak yalnızca hareket eden satırı güncellersiniz. Ayrıca, "inçlerden daha pahalı yüzer", kullanılan uygulamaya ve donanıma bağlıdır. Kuşkusuz, ilave cpu, bir satırı ve ilgili indekslerini güncellemek için gereken cpu ile karşılaştırıldığında önemsizdir.
James Anderson

5
Naysayers için bu çözüm tam olarak Trello'nun ( trello.com ) yaptığı şeydir . Krom hata ayıklayıcınızı açın ve json çıktısını bir yeniden düzenleyiciden önce / sonra (bir kartı sürükleyin / bırakın) ayırın ve - "pos": 1310719, + "pos": 638975.5. Adil olmak gerekirse, çoğu insan içinde 4 milyon giriş bulunan trello listelerini yapmaz, ancak Trello'nun liste büyüklüğü ve kullanım durumu kullanıcı tarafından sıralanabilir içerik için oldukça yaygındır. Kullanıcı tarafından sıralanabilen herhangi bir şeyin yaklaşık olarak yüksek performansla ilgisi yok, int - float sıralama hızı bunun için oldukça fazla, özellikle de veritabanlarının çoğunlukla IO performansı tarafından sınırlandırıldığını düşünüyor.
zelk,

1
@PieterB 'Neden 64 bit bir tamsayı kullanmıyorsunuz?' Demekte, geliştirici için çoğunlukla ergonomi olduğunu söyleyebilirim. Ortalama şamandıralarınız için yaklaşık> 1.0 olduğu gibi yaklaşık <1.0 bit derinliği vardır, bu nedenle 'konum' sütununu 1.0'a ayarlayabilir ve 0,5, 0,25, 0,75'i iki kat daha kolay ekleyebilirsiniz. Tamsayılarla, varsayılan değeriniz 2 ^ 30 ya da daha fazla olmalıdır, hata ayıklama yaparken düşünmeniz biraz zorlaşır. 4073741824, 496359787'den daha büyük mü? Rakamları saymaya başla.
zelk,

1
Dahası, sayılar arasında boşluğun kaldığı bir davaya rastlarsanız ... düzeltmesi o kadar da zor değil. Onlardan birini taşı. Ancak önemli olan, bunun farklı tarafların (örneğin, trello) eşzamanlı düzenlemeleri yürüten en iyi şekilde çalışmasıdır. İki sayıyı bölebilirsiniz, belki biraz rastgele ses serpiştirin ve işte, aynı anda aynı şeyi yapan biri olsa bile, hala küresel bir düzen var ve almak için bir işlemin içine INSERT uygulamanıza gerek yoktu. Orada.
zelk,
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.