• Hallo liebe Userinnen und User,

    nach bereits längeren Planungen und Vorbereitungen sind wir nun von vBulletin auf Xenforo umgestiegen. Die Umstellung musste leider aufgrund der Serverprobleme der letzten Tage notgedrungen vorverlegt werden. Das neue Forum ist soweit voll funktionsfähig, allerdings sind noch nicht alle der gewohnten Funktionen vorhanden. Nach Möglichkeit werden wir sie in den nächsten Wochen nachrüsten. Dafür sollte es nun einige der Probleme lösen, die wir in den letzten Tagen, Wochen und Monaten hatten. Auch der Server ist nun potenter als bei unserem alten Hoster, wodurch wir nun langfristig den Tank mit Bytes vollgetankt haben.

    Anfangs mag die neue Boardsoftware etwas ungewohnt sein, aber man findet sich recht schnell ein. Wir wissen, dass ihr alle Gewohnheitstiere seid, aber gebt dem neuen Board eine Chance.
    Sollte etwas der neuen oder auch gewohnten Funktionen unklar sein, könnt ihr den "Wo issn da der Button zu"-Thread im Feedback nutzen. Bugs meldet ihr bitte im Bugtracker, es wird sicher welche geben die uns noch nicht aufgefallen sind. Ich werde das dann versuchen, halbwegs im Startbeitrag übersichtlich zu halten, was an Arbeit noch aussteht.

    Neu ist, dass die Boardsoftware deutlich besser für Mobiltelefone und diverse Endgeräte geeignet ist und nun auch im mobilen Style alle Funktionen verfügbar sind. Am Desktop findet ihr oben rechts sowohl den Umschalter zwischen hellem und dunklem Style. Am Handy ist der Hell-/Dunkelschalter am Ende der Seite. Damit sollte zukünftig jeder sein Board so konfigurieren können, wie es ihm am liebsten ist.


    Die restlichen Funktionen sollten eigentlich soweit wie gewohnt funktionieren. Einfach mal ein wenig damit spielen oder bei Unklarheiten im Thread nachfragen. Viel Spaß im ngb 2.0.

Mysql: Effizienz von Strukturen

KingJamez

Aktiver NGBler

Registriert
18 Juli 2013
Beiträge
501
Hi,
ich bin in der Planung für ein Projekt, dort möchte ich die Datenbankstruktur so klein wie möglich halten, deshalb denke ich über folgendes Konstrukt nach

Code:
// tablename: entries
id | name | namespace | content 

// tablename: entries_attributes
id | enty_id | name | namespace | content

In "entries" soll jeglicher Hauptinhalt gespeichert werden, bezugnehmend darauf gibt es für den "entries"-table noch einen "entries_attributes"-table. In dem werden zusätzliche Informationen abgelegt.


Ein kleines Beispiel:
Pages haben im "entries"-table einen namespace "app.page". In der Spalte "name" wird der Name der Seite und in "content" der Hauptinhalt gespeichert.
Wenn ich jetzt zb. eine SEO url der Page hinzufügen will, mache ich das über "entries_attributes". Dort wäre der "namespace" "app.page.seourl", in der Spalte "name" stünde "url" und in der spalte content wäre die eigentliche URL. Die Spalte "name" würde bei "entries_attributes" also für die Identifikation dienen (Namen der Spalten sind nicht final ;)).

Messages haben im "entries"-table einen namespace "foobar.message".
Snippets haben im "entries"-table einen namespace "foobar.snippets".

Ist es, auch bei großen Datenmengen, effizient so zu arbeiten oder wäre ein eigener "entry"-table sinnvoller?
Wie sehr bremst das "WHERE namespace = ?".
Der "enties_attributes"-table kann je Eintrag sehr viele Unterpunkte beinhalten, der Abruf geschieht entweder über die "WHERE entry_id = ?" oder über "WHERE entry_id = ? AND namespace = foobar.page.seourl", wenn ich nur bestimmte attribute hole.
 

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.504
Nur für den Fall, dass es dir nicht klar ist: Was du da in Betracht ziehst nennt sich EAV.

imHo:
namespace column solltest du vermutlich indexing.

Performance-Test könntest du doch vermutlich relativ einfach bauen oder? Fände ich auch spannend.
Ansonsten findet sich zum EAV mit mysql vllt was nettes. Grad nur was zu postresql gefunden, weil es da mit JSONB eine interessante Alternative gibt..
Aber willst du wirklich, wirklich EAV einsetzen? ;-).

Vielleicht ist dieser Beitrag auch interessant für dich?
https://stackoverflow.com/a/19368048/1638910
 

KingJamez

Aktiver NGBler

Registriert
18 Juli 2013
Beiträge
501
  • Thread Starter Thread Starter
  • #3
Ich möchte es für bestimmte Dinge einsetzten, nicht gänzlich. Danke für den Link ich versuche das gerade in einem Testcase nachzubilden.
 

KingJamez

Aktiver NGBler

Registriert
18 Juli 2013
Beiträge
501
  • Thread Starter Thread Starter
  • #4
Da es ein private Projekt ist, komme ich leider seltener dazu. Habe mal ein Konstrukt gebastelt um den Unterschied der Geschwindigkeit zu visualisieren.

Als content kommt jeweils ein text mit 593 Zeichen zum Einsatz (Lorem Ipsum). In der Datenbank befinden sich 100.000 Einträge. Das holen der Einträge habe ich auf 10.000 beschränkt.

Tabellen

Normaler Weg:
[src=php]
Data_Reg: id | content
[/src]

Attribut Weg:
[src=php]
Data_Attr: id | name
Data_Attributes: id | ref_id | content
[/src]


Queries

Normaler Weg:
[src=php]
SELECT * FROM Data_reg LIMIT 10000
[/src]

Attribut Weg:
[src=php]
"SELECT Data_Attr.id, DA.id, DA.content FROM attrway LEFT JOIN Data_Attributes DA ON DA.ref_id = Data_Attr.id LIMIT 10000"
[/src]


Zeiten

Normaler Weg: 0.05389404296875 Sekunden
Attribut Weg: 24.86952996254 Sekunden



MySQL: Ver 15.1 Distrib 10.2.10-MariaDB, for osx10.13 (x86_64)
PHP: PHP 7.1.14
Zum Abfragen wurde PHP´s PDO genutzt.

PHP, MySQL und der Client waren in meinem Test der gleiche PC.
 

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.504
Kannst du die Unterschiede in den Tabellen noch etwas ausführen? Das leuchtet mir spontan nicht ein.
Würde glaube ich zwischendurch just for fun was in postgresql nachbauen, wenn du die Testdaten zur Verfügung stellst.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.561
Mal zwei Punkte in den Raum geworfen, die mir spontan auffallen, wo ich Fallstricke sehe, bezogen auf den zweiten Weg:

Ist das nicht logisch dass das zweite Konstrukt mehr Zeit in Anspruch nimmt?, Es sind doch mehr "Aufgaben"/Operationen für die DB - den Query zu erzeugen, den JOIN durchzuführen, die Attribute, von davor "zusammengeführten" (?) Daten des JOIN? (RAM?) zu behandeln, daraus zu filtern bzw. zu selektieren. Und dann kommt die Rückgabe.

KISS Prinzip? - Keep it simple stupid? Gilt das nicht auch für eine DB?
Und was ist üblicher und somit auch optimierter als "Use-Case" ? Vermutlich der einfache Query, ohne "Bedingungen".

Auf der anderen Seite müßte man jetzt aber auch bedenken, du brauchst für den zweiten Query viel länger, aber wäre die Zeit der Datenverarbeitung um an die Inhalte zu kommen, auch langsamer oder gewinnst du da Zeit gegenüber Weg 1? - Wäre vielleicht schon der nächste Schritt, aber auch interessant.

Aber auch RAM Usage würde mich interessieren, bzw. wie ein "JOIN" virtuell arbeitet. Aber vielleicht ist die Frage auch etwas fehl am Platz. ;)
 
Zuletzt bearbeitet:

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.504
KISS Prinzip? - Keep it simple stupid? Gilt das nicht auch für eine DB?
Und was ist üblicher und somit auch optimierter als "Use-Case" ? Vermutlich der einfache Query, ohne "Bedingungen".
Darauf gibt es halt zuweilen nicht die eine Antwort, da du nur tradeoffs hast und am Ende qualitativ abwägen musst, was in deinem speziellen Fall der bessere Kompromiss ist.
Typisches Beispiel sind z.B. Schreib- vs. Lesevorgänge. Manchmal hast du einen Ansatz, wo nur Lesevorgänge schnell sind und einen anderen, wo nur Schreibvorgänge schnell sind. Du musst dann abwägen, was in dem vorliegenden Anwendungsfall wichtiger ist.

An sich imHo verwendet man EAV nur wenn einem wirklich nichts besseres einfällt, weil man damit viele Vorteile eines relationalen DBMS aushebelt.
 

Rakorium-M

NGBler

Registriert
14 Juli 2013
Beiträge
413
Die Struktur ist so lahm, weil MySQL da standardmäßig keinen Index anlegt. Ich hab mal dein Beispiel nachgebaut (und mit zufälligen Texten befüllt, MariaDB 10.1):
[src=mysql]
-- Versuch 1: Struktur wie oben, 100000 zufällige Einträge
CREATE TABLE data_attr (
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(255) NOT NULL
);
CREATE TABLE data_attributes (
id int PRIMARY KEY AUTO_INCREMENT,
ref_id int NOT NULL,
content TEXT NOT NULL
);
-- => 17.571578025818 sec
-- => 16.847698926926

-- Versuch 2: FOREIGN KEY constraints an die Tabelle
DROP TABLE TABLE data_attributes;
CREATE TABLE data_attributes (
id int PRIMARY KEY AUTO_INCREMENT,
ref_id int NOT NULL REFERENCES data_attr ON DELETE CASCADE,
content TEXT NOT NULL
);
-- => 16.15616106987

-- Versuch 3: Index manuell anlegen
CREATE INDEX data_attributes_ref_id_idx ON data_attributes (ref_id);
-- => 0.028398990631104
-- => 0.029699087142944[/src]
0.03 sec sieht doch gar nicht so schlecht aus, oder?

Zum Nachvollziehen:
[src=php]<?php
error_reporting(E_ALL);
ini_set('display_errors', true);

$pdo = new PDO('mysql:host=localhost;dbname=performance_tests', 'username', 'password');

if (isset($_GET['insert'])) {
echo "Insert...\n";
$t = microtime(true);
$pdo->query('DELETE FROM data_attributes;');
$pdo->query('DELETE FROM data_attr;');

$statement1 = $pdo->prepare("INSERT INTO data_attr (id, name) VALUES (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?)");
$statement2 = $pdo->prepare("INSERT INTO data_attributes (ref_id, content) VALUES (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?)");

for ($i = 1; $i < 100000; $i += 50) {
$v1 = [];
$v2 = [];
for ($j = $i; $j < $i+50; $j++) {
$v1[] = $j;
$v1[] = bin2hex(random_bytes(8));
$v2[] = $j;
$v2[] = base64_encode(random_bytes(444));
}
$statement1->execute($v1);
$statement2->execute($v2);

if (($i+19) % 10000 == 0) {
var_dump($i+49);
flush();
}
}

var_dump(microtime(true) - $t);
}


var_dump($pdo->query("SELECT count(*) FROM data_attr")->fetch());
var_dump($pdo->query("SELECT count(*) FROM data_attributes")->fetch());
flush();

// ANFRAGE
$t = microtime(true);
$sql = "SELECT data_attr.id, DA.id, DA.content FROM data_attr LEFT JOIN data_attributes DA ON DA.ref_id = data_attr.id LIMIT 10000";
$result = $pdo->query($sql);
var_dump(microtime(true) - $t);
[/src]


Fun fact: Das selbe mit Postgresql 10. Postgres scheint selbst ohne jegliche Indices einen effizienten Algorithmus dafür zu haben. Was meine Meinung wieder bestätigt, dass der einzige Vorteil von MySQL seine Verbreitung ist...
[src=postgresql]CREATE TABLE data_attr (
id serial PRIMARY KEY,
name varchar(255) NOT NULL
);
CREATE TABLE data_attributes (
id serial PRIMARY KEY,
ref_id int NOT NULL,
content TEXT NOT NULL
);
-- => 0.042082071304321
-- => 0.048994064331055

DROP TABLE data_attributes;
CREATE TABLE data_attributes (
id serial PRIMARY KEY,
ref_id int NOT NULL REFERENCES data_attr ON DELETE CASCADE,
content TEXT NOT NULL
);
-- => 0.058371067047119
-- => 0.057842969894409

CREATE INDEX data_attributes_ref_id_idx ON data_attributes (ref_id);
-- => 0.018332004547119
-- => 0.017482042312622[/src]
 
Zuletzt bearbeitet:

drfuture

Zeitreisender
Teammitglied

Registriert
14 Juli 2013
Beiträge
8.730
Ort
in der Zukunft
@TS Es dürfte schlicht auch stark darauf ankommen was du eigentlich mit den Daten machen möchtest.
Ein "Neuesten Beitrag anzeigen", "Anzahl der Beiträge", "Anzahl zeit" .. usw. ist denke ich bei deinem Vorschlag langsamer.
Alles was zählen, Sortieren, Berechnungen usw. beinhaltet und so auf einer größeren Datenbasis stattfinden muss.

Reine selects dürften relativ gleich schnell sein bei beiden Verfahren.
Du baust dir so aber im Zweifel vielleicht doppelte Inhalte oder rekursionen in die eine Tabelle. Auch das kommt auf den Inhalt und die Struktur der Daten an.
 
Oben