python - So ersetzen Sie den Primärschlüssel von Django durch eine andere Ganzzahl, die für diese Tabelle eindeutig ist




mysql hash (3)

Die Idee

Ich würde Ihnen den gleichen Ansatz empfehlen, den Instragam . Ihre Anforderungen scheinen genau Ihren zu entsprechen.

Generierte IDs sollten nach Zeit sortierbar sein (so dass beispielsweise eine Liste von Foto-IDs sortiert werden kann, ohne weitere Informationen zu den Fotos abzurufen). IDs sollten idealerweise 64 Bit lang sein (für kleinere Indizes und bessere Speicherung in Systemen wie Redis) sollte so wenig neue "bewegliche Teile" wie möglich einführen - ein großer Teil davon, wie wir Instagram mit sehr wenigen Ingenieuren skalieren konnten, ist die Auswahl einfacher, leicht verständlicher Lösungen, denen wir vertrauen.

Sie entwickelten ein System mit 41 Bit basierend auf dem Zeitstempel, 13 Bit auf dem Datenbank-Shard und 10 Bit für einen Auto-Inkrement-Teil. Da Sie anscheinend keine Scherben verwenden. Sie können nur 41 Bits für ein zeitbasiertes Copmonent und 23 willkürlich ausgewählte Bits haben. Dies führt zu einer äußerst unwahrscheinlichen Wahrscheinlichkeit von 1 zu 8,3 Millionen, dass ein Konflikt auftritt, wenn Sie gleichzeitig Datensätze einfügen. In der Praxis ist es jedoch unwahrscheinlich, dass Sie dies erreichen. Richtig, wie wäre es mit Code:

IDs generieren

START_TIME = a constant that represents a unix timestamp

def make_id():
    '''
    inspired by http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram
        '''

    t = int(time.time()*1000) - START_TIME
    u = random.SystemRandom().getrandbits(23)
    id = (t << 23 ) | u

    return id


def reverse_id(id):
    t  = id >> 23
    return t + START_TIME 

Beachten Sie, dass START_TIME im obigen Code eine beliebige Startzeit ist. Sie können time.time () * 1000 verwenden, den Wert START_TIME und als START_TIME

Beachten Sie, dass Sie mit der von reverse_id Methode herausfinden können, zu welchem ​​Zeitpunkt der Datensatz erstellt wurde. Wenn Sie diese Informationen nachverfolgen müssen, können Sie dies tun, ohne ein weiteres Feld dafür hinzufügen zu müssen! Ihr Primärschlüssel speichert also Ihren Speicherplatz, anstatt ihn zu vergrößern!

Das Model

Nun, so würde Ihr Modell aussehen.

class MyClass(models.Model):
   id = models.BigIntegerField(default = fields.make_id, primary_key=True)  

Wenn Sie Änderungen an Ihrer Datenbank außerhalb von Django vornehmen, müssen Sie das Äquivalent von make_id als SQL-Funktion erstellen

Als Fußnote. Dies ähnelt in etwa dem Ansatz, den Mongodb verwendet, um die _ID für jedes Objekt zu generieren.

Ich habe eine Django-Webanwendung, die die standardmäßig automatisch inkrementierten positiven Ganzzahlen als Primärschlüssel verwendet. Dieser Schlüssel wird in der gesamten Anwendung verwendet und häufig in die URL eingefügt. Ich möchte diese Nummer nicht der Öffentlichkeit zugänglich machen, damit sie die Anzahl der Benutzer oder anderer Entitäten in meiner Datenbank erraten kann.

Dies ist eine häufige Anforderung und ich habe Fragen zu ähnlichen mir mit Antworten gesehen. Die meisten Lösungen empfehlen das Hashing des ursprünglichen Primärschlüsselwerts. Keine dieser Antworten entspricht jedoch genau meinen Anforderungen. Das sind meine Anforderungen:

  1. Ich möchte den Feldtyp des Primärschlüssels als Ganzzahl beibehalten.
  2. Ich würde es auch vorziehen, diesen Wert nicht bei jedem Lesen, Schreiben oder Vergleichen mit der Datenbank zu hacken oder zu enthacken. Das scheint verschwenderisch. Es wäre schön, es nur einmal zu tun: Wenn der Datensatz zum ersten Mal in die Datenbank eingefügt wird
  3. Die Hashing- / Verschlüsselungsfunktion muss nicht umkehrbar sein, da ich den ursprünglichen sequentiellen Schlüssel nicht wiederherstellen muss. Der Hash-Wert muss nur eindeutig sein.
  4. Der Hash-Wert muss NUR für diese Tabelle eindeutig sein - nicht universell eindeutig.
  5. Der Hash-Wert sollte so kurz wie möglich sein. Ich möchte extrem lange (über 20 Zeichen) URLs vermeiden

Was ist der beste Weg, um dies zu erreichen? Würde das folgende funktionieren?

def hash_function(int):
    return fancy-hash-function # What function should I use??


def obfuscate_pk(sender, instance, created, **kwargs):
    if created:
        logger.info("MyClass #%s, created with created=%s: %s" % (instance.pk, created, instance))
        instance.pk = hash_function(instance.pk)
        instance.save()
        logger.info("\tNew Pk=%s" % instance.pk)

class MyClass(models.Model):
    blahblah = models.CharField(max_length=50, null=False, blank=False,)


post_save.connect(obfuscate_pk, sender=MyClass)

Behalten Sie das AUTO_INCREMENT , aber geben Sie es auf halb geheime Weise weiter: In einem Cookie. Es ist ein bisschen Code erforderlich, um das Cookie zu erstellen, zu setzen und zu lesen. Cookies sind jedoch allen anderen als ernsthaften Hackern verborgen.


Sie müssen zwei Anliegen trennen:

  1. Der Primärschlüssel, derzeit eine automatisch inkrementierende Ganzzahl, ist die beste Wahl für eine einfache, relativ vorhersagbare eindeutige Kennung, die auf Datenbankebene erzwungen werden kann.

  2. Das bedeutet nicht, dass Sie es Benutzern in Ihren URLs anzeigen müssen.

Es wird empfohlen, Ihrem Modell ein neues UUID-Feld hinzuzufügen und Ihre Ansichten neu zuzuordnen, um es anstelle des PK für die Objektsuche zu verwenden.





primary-key