Das Team ersetzte den neuen Rahmen. Alle neuen Unternehmen verwenden ein neues Framework und sogar eine neue Datenbank – MySQL. Wir haben Oracle bereits zuvor verwendet und verschiedene Bestellnummern, Seriennummern, Chargennummern usw. sind allesamt direkt digitale Seriennummern, die von der Sequenz von Oracle bereitgestellt werden. Nachdem die Datenbank nun auf MySQL umgestellt wurde, ist es offensichtlich, dass die alte Methode nicht mehr anwendbar ist. Muss ein neues schreiben: • Verteilte Szenennutzung •Erfüllen Sie bestimmte Parallelitätsanforderungen Ich habe einige relevante Informationen gefunden und festgestellt, dass die Implementierung von MySQL in dieser Hinsicht auf dem Prinzip eines Datenbankdatensatzes und der ständigen Aktualisierung seines Werts basiert. Dann verwenden die meisten Implementierungslösungen Funktionen. Fügen Sie den Code aus dem Internet ein: Implementierung basierend auf MySQL-Funktion Tabellenstruktur CREATE TABLE `t_sequence` ( `sequence_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'Sequenzname' , `Wert` int(11) NULL DEFAULT NULL COMMENT 'Aktueller Wert' , PRIMÄRSCHLÜSSEL (`Sequenzname`) ) ENGINE=InnoDB STANDARDZEICHENSATZ=utf8 COLLATE=utf8_general_ci ROW_FORMAT=KOMPAKT ; Den nächsten Wert abrufen CREATE DEFINER = `root`@`localhost` FUNCTION `nextval`(Sequenzname varchar(64)) Gibt int(11) zurück. BEGINNEN aktuelle Ganzzahl deklarieren; Strom einstellen = 0; Aktualisiere t_Sequenz t setze t.Wert = t.Wert + 1, wobei t.Sequenzname = Sequenzname; Wählen Sie t.value in current aus t_sequence t, wobei t.sequence_name = sequence_name; Rückstrom; Ende; Bei gleichzeitigen Szenarien können Probleme auftreten. Obwohl auf der Geschäftsebene Sperren hinzugefügt werden können, kann dies bei verteilten Szenarien nicht garantiert werden und die Effizienz wird nicht hoch sein. Implementieren Sie selbst eine, Java-Version Prinzip: • Lesen Sie einen Datensatz, speichern Sie ein Datensegment zwischen, z. B. 0-100, und ändern Sie den aktuellen Wert des Datensatzes von 0 auf 100 • Aktualisieren Sie die optimistische Datenbanksperre, um Wiederholungsversuche zu ermöglichen • Daten aus dem Cache lesen und dann aus der Datenbank lesen, nachdem dieser aufgebraucht ist Kein Unsinn, hier ist der Code: Java-basierte Implementierung Tabellenstruktur Jedes Update setzt SEQ_VALUE auf SEQ_VALUE+STEP CREATE TABLE `t_pub_sequence` ( `SEQ_NAME` varchar(128) CHARACTER SET utf8 NOT NULL COMMENT 'Sequenzname', `SEQ_VALUE` bigint(20) NOT NULL COMMENT 'Aktueller Sequenzwert', `MIN_VALUE` bigint(20) NOT NULL COMMENT 'Minimalwert', `MAX_VALUE` bigint(20) NOT NULL COMMENT 'Maximaler Wert', `STEP` bigint(20) NOT NULL COMMENT 'Die Anzahl der Werte, die jedes Mal übernommen werden', `TM_CREATE` datetime NOT NULL COMMENT 'Erstellungszeit', `TM_SMP` datetime NICHT NULL STANDARD CURRENT_TIMESTAMP BEI UPDATE CURRENT_TIMESTAMP KOMMENTAR 'Änderungszeitpunkt', PRIMÄRSCHLÜSSEL (`SEQ_NAME`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tabelle zur Seriennummerngenerierung'; Sequenzschnittstelle /** * <p></p> * @Autor Coderzl * @Titel MysqlSequence * @Description Sequenz basierend auf MySQL-Datenbank * @date 2017/6/6 23:03 */ öffentliche Schnittstelle MysqlSequence { /** * <p> * Holen Sie sich die Seriennummer der angegebenen Sequenz * </p> * @param seqName Sequenzname* @return String Sequenznummer*/ öffentliche Zeichenfolge nextVal (Zeichenfolgename); } Sequenzintervall Wird verwendet, um eine Sequenz lokal von Minimum bis Maximum zwischenzuspeichern. /** * <p></p> * * @Autor Coderzl * @Titelsequenzbereich * @Description Sequenzintervall, das zum Zwischenspeichern der Sequenz verwendet wird * @date 2017/6/6 22:58 */ @Daten öffentliche Klasse SequenceRange { privates letztes langes min; privates endgültiges langes Maximum; /** */ privater endgültiger AtomicLong-Wert; /** Liegt es über dem Limit? */ privater flüchtiger Boolescher Wert über = false; /** * Konstruktion. * * @param min * @param max */ öffentlicher Sequenzbereich(lang min, lang max) { dies.min = min; dies.max = max; dieser.Wert = neues AtomicLong(min); } /** * <p>Ruft ab und erhöht</p> * * @zurückkehren */ öffentliche lange getAndIncrement() { langer aktuellerWert = Wert.getAndIncrement(); wenn (aktuellerWert > max) { über = wahr; Rückgabe -1; } aktuelleWert zurückgeben; } } BO Entsprechender Datenbankeintrag @Daten öffentliche Klasse MysqlSequenceBo { /** * Sequenzname */ privater String-Sequenzname; /** * Aktueller Wert */ privater langer Sequenzwert; /** * Mindestwert */ privater langer Mindestwert; /** * Maximalwert */ privater langer Maximalwert; /** * Die Anzahl der jeweils erfassten Werte */ privat Langer Schritt; /** */ privates Datum tmCreate; /** */ privates Datum tmSmp; öffentliches Boolean validieren(){ //Einige einfache Prüfungen. Beispielsweise muss der aktuelle Wert zwischen dem Maximal- und Minimalwert liegen. Schrittwert kann nicht größer sein als die Differenz zwischen Max und Minif (StringUtil.isBlank(seqName) || minValue < 0 || maxValue <= 0 || Schritt <= 0 || minValue >= maxValue || maxValue - minValue <= Schritt ||seqValue < minValue || seqValue > maxValue ) { gibt false zurück; } gibt true zurück; } } DAO Hinzufügen, Löschen, Ändern und Prüfen. Tatsächlich verwenden Sie die Änderungs- und Abfragefunktion öffentliche Schnittstelle MysqlSequenceDAO { /** * */ öffentliche int createSequence(MysqlSequenceBo bo); öffentliche int updSequence(@Param("seqName") String seqName, @Param("alterWert") langer alterWert ,@Param("neuerWert") langer neuerWert); öffentliche int delSequence(@Param("seqName") String seqName); öffentliche MysqlSequenceBo getSequence(@Param("seqName") String seqName); öffentliche Liste<MysqlSequenceBo> getAll(); } Mapper <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.xxxxx.core.sequence.impl.dao.MysqlSequenceDAO" > <resultMap id="BaseResultMap" Typ="com.xxxxx.core.sequence.impl.MysqlSequenceBo" > <Ergebnisspalte="SEQ_NAME" Eigenschaft="seqName" jdbcType="VARCHAR" /> <Ergebnisspalte="SEQ_VALUE" Eigenschaft="seqValue" jdbcType="BIGINT" /> <Ergebnis Spalte="MIN_VALUE" Eigenschaft="minValue" jdbcType="BIGINT" /> <Ergebnis Spalte="MAX_VALUE" Eigenschaft="maxValue" jdbcType="BIGINT" /> <Ergebnis Spalte="SCHRITT" Eigenschaft="Schritt" jdbcType="BIGINT" /> <Ergebnisspalte="TM_CREATE" Eigenschaft="tmCreate" jdbcType="TIMESTAMP" /> <Ergebnisspalte="TM_SMP" Eigenschaft="tmSmp" jdbcType="TIMESTAMP" /> </resultMap> <delete id="delSequence" parameterType="java.lang.String" > aus t_pub_sequence löschen wobei SEQ_NAME = #{seqName,jdbcType=VARCHAR} </löschen> <insert id="createSequence" parameterType="com.xxxxx.core.sequence.impl.MysqlSequenceBo" > einfügen in t_pub_sequence (SEQ_NAME,SEQ_VALUE,MIN_VALUE,MAX_VALUE,STEP,TM_CREATE) Werte (#{seqName,jdbcType=VARCHAR}, #{seqValue,jdbcType=BIGINT}, #{minValue,jdbcType=BIGINT}, #{maxValue,jdbcType=BIGINT}, #{Schritt,jdbcType=BIGINT}, Jetzt()) </einfügen> <update id="updSequence" parameterType="com.xxxxx.core.sequence.impl.MysqlSequenceBo" > t_pub_sequence aktualisieren setze SEQ_VALUE = #{newValue,jdbcType=BIGINT} wobei SEQ_NAME = #{seqName,jdbcType=VARCHAR} und SEQ_VALUE = #{oldValue,jdbcType=BIGINT} </Aktualisieren> <select id="getAll" resultMap="BaseResultMap" > Wählen Sie SEQ_NAME, SEQ_VALUE, MIN_VALUE, MAX_VALUE, STEP von t_pub_sequence </Auswählen> <select id="getSequence" resultMap="BaseResultMap" > Wählen Sie SEQ_NAME, SEQ_VALUE, MIN_VALUE, MAX_VALUE, STEP von t_pub_sequence wobei SEQ_NAME = #{seqName,jdbcType=VARCHAR} </Auswählen> </mapper> Schnittstellenimplementierung @Repository("mysqlSequenz") öffentliche Klasse MysqlSequenceImpl implementiert MysqlSequence{ @Autowired private MysqlSequenceFactory mysqlSequenceFactory; /** * <p> * Holen Sie sich die Seriennummer der angegebenen Sequenz * </p> * * @param seqName Sequenzname * @return String Sequenznummer * @author coderzl */ @Überschreiben öffentliche Zeichenfolge nextVal(Zeichenfolgename) { gibt Objects.toString zurück (mysqlSequenceFactory.getNextVal (seqName)); } } Fabrik Die Fabrik hat nur zwei Dinge getan •Wenn der Dienst startet, initialisieren Sie alle Sequenzen in der Datenbank [vollständiger Sequenzintervall-Cache] • Den nächsten Wert der Sequenz abrufen @Komponente öffentliche Klasse MysqlSequenceFactory { privates endgültiges Lock-Lock = neues ReentrantLock(); /** */ private Map<String,MysqlSequenceHolder> holderMap = neue ConcurrentHashMap<>(); @Autowired privates MysqlSequenceDAO msqlSequenceDAO; /** Die Anzahl der Wiederholungsversuche für die fehlgeschlagene Initialisierung eines optimistischen Sperrupdates für eine einzelne Sequenz*/ @Wert("${seq.init.retry:5}") private int initRetryNum; /** Die Anzahl der Wiederholungsversuche für fehlgeschlagene optimistische Sperraktualisierungen eines einzelnen Sequenzintervalls*/ @Wert("${seq.get.retry:20}") private int getRetryNum; @PostKonstrukt private void init(){ //Alle Sequenzen initialisieren initAll(); } /** * <p>Alle Sequenzen in die Tabelle laden und Initialisierung abschließen</p> * @return ungültig * @Autor Coderzl */ private void initAll(){ versuchen { sperre.sperre(); Liste<MysqlSequenceBo> boList = msqlSequenceDAO.getAll(); wenn (boList == null) { throw new IllegalArgumentException("Der Sequenzdatensatz ist null!"); } für (MysqlSequenceBo bo : boList) { MysqlSequenceHolder-Inhaber = neuer MysqlSequenceHolder (msqlSequenceDAO, bo, initRetryNum, getRetryNum); halter.init(); holderMap.put(bo.getSeqName(), Inhaber); } }Endlich { sperren.entsperren(); } } /** * <p> </p> * @param Sequenzname * @return lang * @Autor Coderzl */ öffentliche lange getNextVal(String seqName){ MysqlSequenceHolder-Inhaber = holderMap.get(seqName); wenn (Inhaber == null) { versuchen { sperre.sperre(); Inhaber = InhaberMap.get(seqName); wenn (Inhaber != null){ gibt holder.getNextVal() zurück; } MysqlSequenceBo bo = msqlSequenceDAO.getSequence(seqName); Halter = neuer MysqlSequenceHolder(msqlSequenceDAO, bo,initRetryNum,getRetryNum); halter.init(); holderMap.put(seqName, Inhaber); }Endlich { sperren.entsperren(); } } gibt holder.getNextVal() zurück; } } Einzelsequenzhalter • init() Die Initialisierung umfasst die Parameterüberprüfung, die Aktualisierung des Datenbankeintrags und die Erstellung von Sequenzintervallen •getNextVal() Holt den nächsten Wert öffentliche Klasse MysqlSequenceHolder { privates endgültiges Lock-Lock = neues ReentrantLock(); /** Sequenzname */ privater String-Sequenzname; /** SequenzDao */ private MysqlSequenceDAO-SequenzDAO; private MysqlSequenceBo-SequenzBo; /** */ privater SequenceRange-Sequenzbereich; /** Ob initialisiert werden soll */ privater flüchtiger Boolescher Wert ist Initialisieren = false; /**Wiederholungsversuche für die Sequenzinitialisierung*/ private int initRetryNum; /**Sequenz erhält die Anzahl der Wiederholungsversuche*/ private int getRetryNum; /** * <p>Konstruktor</p> * @Titel MysqlSequenceHolder * @param sequenceDAO * @param sequenceBo * @param initRetryNum Die Anzahl der Wiederholungsversuche, nachdem eine Datenbankaktualisierung während der Initialisierung fehlgeschlagen ist. * @param getRetryNum Die Anzahl der Wiederholungsversuche, nachdem eine Datenbankaktualisierung beim Abrufen von nextVal fehlgeschlagen ist. * @return * @Autor Coderzl */ öffentliche MysqlSequenceHolder(MysqlSequenceDAO sequenceDAO, MysqlSequenceBo sequenceBo, int initRetryNum, int getRetryNum) { dies.sequenceDAO = sequenceDAO; diese.sequenzBo = sequenzBo; this.initRetryNum = initRetryNum; Dies.getRetryNum = getRetryNum; wenn(SequenzBo != null) this.seqName = sequenceBo.getSeqName(); } /** * <p> Initialisierung </p> * @Title init * @param * @return ungültig * @Autor Coderzl */ öffentliche void init(){ wenn (istInitialisieren == wahr) { throw new SequenceException("[" + seqName + "] der MysqlSequenceHolder wurde initialisiert"); } wenn (sequenceDAO == null) { throw new SequenceException("[" + seqName + "] das sequenceDao ist null"); } wenn (seqName == null || seqName.trim().length() == 0) { throw new SequenceException("[" + seqName + "] der Sequenzname ist null"); } wenn (SequenzBo == null) { throw new SequenceException("[" + seqName + "] die sequenceBo ist null"); } wenn (!sequenceBo.validate()){ throw new SequenceException("[" + seqName + "] die Validierung von sequenceBo ist fehlgeschlagen. BO:"+sequenceBo); } // Initialisiere die Sequenz versuchen { initSequenceRecord(sequenceBo); } Fang (Sequenzausnahme e) { werfen e; } istInitialisieren = wahr; } /** * <p>Holen Sie sich die nächste Sequenznummer</p> * @Title getNextVal * @param * @return lang * @Autor Coderzl */ öffentliche lange getNextVal(){ wenn(istInitialisieren == false){ throw new SequenceException("[" + seqName + "] der MysqlSequenceHolder nicht initialisiert"); } wenn (Sequenzbereich == null) { throw new SequenceException("[" + seqName + "] der sequenceRange ist null"); } langer aktueller Wert = sequenceRange.getAndIncrement(); wenn(aktuellerWert == -1){ versuchen{ sperre.sperre(); aktuellerWert = sequenceRange.getAndIncrement(); wenn(aktuellerWert != -1){ gibt aktuellen Wert zurück; } Sequenzbereich = Wiederholungsbereich(); aktuellerWert = sequenceRange.getAndIncrement(); }Endlich { sperren.entsperren(); } } gibt aktuellen Wert zurück; } /** * <p> Initialisieren Sie den aktuellen Datensatz </p> * @Title initSequenceRecord * @Beschreibung * @param sequenceBo * @return ungültig * @Autor Coderzl */ private void initSequenceRecord(MysqlSequenceBo sequenceBo){ //Optimistische Sperre aktualisiert Datenbankeinträge innerhalb einer begrenzten Anzahl von Malen for(int i = 1; i < initRetryNum; i++){ //Abfrage bo MysqlSequenceBo curBo = sequenceDAO.getSequence(sequenceBo.getSeqName()); wenn(curBo == null){ throw new SequenceException("[" + seqName + "] die aktuelle sequenceBo ist null"); } wenn (!curBo.validate()){ throw new SequenceException("[" + seqName + "] die Validierung der aktuellen sequenceBo ist fehlgeschlagen"); } //Ändern Sie den aktuellen Wert long newValue = curBo.getSeqValue()+curBo.getStep(); //Überprüfe den aktuellen Wert if(!checkCurrentValue(newValue,curBo)){ neuerWert = ResetAktuellerWert(curBo); } int Ergebnis = sequenceDAO.updSequence(sequenceBo.getSeqName(),curBo.getSeqValue(),newValue); wenn(Ergebnis > 0){ Sequenzbereich = neuer Sequenzbereich (curBo.getSeqValue(), neuerWert - 1); curBo.setSeqValue(neuerWert); diese.sequenceBo = curBo; zurückkehren; }anders{ weitermachen; } } //Wenn das Update innerhalb der angegebenen Anzahl von Malen fehlschlägt, wird eine Ausnahme ausgelöst. throw new SequenceException("[" + seqName + "] sequenceBo update error"); } /** * <p>Überprüfen Sie, ob der neue Wert zulässig ist und ob der neue aktuelle Wert zwischen den Maximal- und Minimalwerten liegt.</p> * @param aktueller Wert * @param curBo * @return Boolescher Wert * @Autor Coderzl */ privater Boolescher Wert checkCurrentValue(langer aktuellerWert,MysqlSequenceBo aktuellerWert){ wenn(aktuellerWert > curBo.getMinValue() und aktuellerWert <= curBo.getMaxValue()){ gibt true zurück; } gibt false zurück; } /** * <p> Aktuellen Wert der Sequenz zurücksetzen: Wenn die aktuelle Sequenz den Maximalwert erreicht, beginnt sie wieder beim Minimalwert.</p> * @Titel setzt aktuellen Wert zurück * @param curBo * @return lang * @Autor Coderzl */ private long resetCurrentValue(MysqlSequenceBo curBo){ gibt curBo.getMinValue() zurück; } /** * <p>Wenn das Cache-Intervall aufgebraucht ist, lesen Sie die Datenbankeinträge erneut und speichern Sie das neue Sequenzsegment im Cache.</p> * @Title Wiederholungsbereich * @param SequenceRange * @Autor Coderzl */ privater Sequenzbereich retryRange(){ für(int i = 1; i < getRetryNum; i++){ //Abfrage bo MysqlSequenceBo curBo = sequenceDAO.getSequence(sequenceBo.getSeqName()); wenn(curBo == null){ throw new SequenceException("[" + seqName + "] die aktuelle sequenceBo ist null"); } wenn (!curBo.validate()){ throw new SequenceException("[" + seqName + "] die Validierung der aktuellen sequenceBo ist fehlgeschlagen"); } //Ändern Sie den aktuellen Wert long newValue = curBo.getSeqValue()+curBo.getStep(); //Überprüfe den aktuellen Wert if(!checkCurrentValue(newValue,curBo)){ neuerWert = ResetAktuellerWert(curBo); } int Ergebnis = sequenceDAO.updSequence(sequenceBo.getSeqName(),curBo.getSeqValue(),newValue); wenn(Ergebnis > 0){ Sequenzbereich = neuer Sequenzbereich (curBo.getSeqValue(), neuerWert - 1); curBo.setSeqValue(neuerWert); diese.sequenceBo = curBo; Sequenzbereich zurückgeben; }anders{ weitermachen; } } neue SequenceException werfen("[" + seqName + "] sequenceBo-Aktualisierungsfehler"); } } Zusammenfassen • Wenn der Dienst neu gestartet wird oder anormal ist, geht die zwischengespeicherte und vom aktuellen Dienst nicht verwendete Sequenz verloren •In verteilten Szenarien, wenn mehrere Dienste gleichzeitig initialisiert werden oder Sequenzen erneut abgerufen werden, stellt eine optimistische Sperre sicher, dass sie nicht miteinander in Konflikt geraten. Dienst A erhält 0-99, Dienst B 100-199 und so weiter. •Wenn die Sequenz häufig abgerufen wird, kann eine Erhöhung des Schrittwerts die Leistung verbessern. Aber gleichzeitig gehen bei einem anormalen Service mehr Sequenzen verloren • Ändern Sie einige Attributwerte der Sequenz in der Datenbank, z. B. Schritt, Maximum usw., und die neuen Parameter werden beim nächsten Abrufen aus der Datenbank aktiviert •sequence bietet nur eine begrenzte Anzahl von Sequenznummern (bis max-min). Sobald das Maximum erreicht ist, wird eine Schleife gestartet und von vorne begonnen. •Da die Sequenz wiederholt wird, ist sie nach Erreichen des Maximums nicht mehr eindeutig, wenn Sie sie erneut abrufen. Es wird empfohlen, zum Erstellen von Geschäftsseriennummern und zur Spleißzeit eine Sequenz zu verwenden. Beispiel: 20170612235101+Seriennummer Methode zum Zusammenfügen von Geschäfts-IDs @Service öffentliche Klasse JrnGeneratorService { private statische endgültige Zeichenfolge SEQ_NAME = "T_SEQ_TEST"; /** Sequenzdienst */ @Autowired private MySqlSequence mySqlSequence; öffentliche Zeichenfolge generierenJrn() { versuchen { Zeichenfolgensequenz = mySqlSequence.getNextValue(SEQ_NAME); Sequenz = linkeAuffüllung(Sequenz,8); Kalender Kalender = Kalender.getInstance(); SimpleDateFormat sDateFormat = neues SimpleDateFormat("yyyyMMddHHmmss"); Zeichenfolge nowdate = sDateFormat.format(calendar.getTime()); nowdate.substring(4, nowdate.length()); String jrn = nowdate + sequence + RandomUtil.getFixedLengthRandom(6); //10-stellige Zeit + 8-stellige Sequenz + 6-stellige Zufallszahl = 24-stellige Seriennummer return jrn; } Fang (Ausnahme e) { //ZU TUN } } private String leftPadding(String seq,int len){ Zeichenfolge res = ""; Zeichenfolge str = ""; wenn(seq.length()<len){ für(int i=0;i<len-seq.length();i++){ str +="0"; } } res = str + seq; Rückgabewert; } } Die obige auf MySQL basierende Sequenzimplementierungsmethode ist alles, was ich mit Ihnen teilen möchte. Ich hoffe, sie kann Ihnen als Referenz dienen. Ich hoffe auch, dass Sie 123WORDPRESS.COM unterstützen werden. Das könnte Sie auch interessieren:
|
<<: So fügen Sie einem laufenden Docker-Container dynamisch ein Volume hinzu
>>: Detaillierte Erläuterung der Verwendung von Requisiten in den drei Hauptattributen von React
Wenn Sie nginx als Reverse-Proxy verwenden, könne...
Ich habe viele Tutorials gelesen, aber festgestel...
Vorwort Die Serversystemumgebung ist: CentOS 6.5 ...
Das Installationstutorial für mysql5.7.17 wird Ih...
Dieser Artikel beschreibt hauptsächlich, wie die ...
Machen Sie einen leeren Bereich für Taobao: Wenn ...
JavaScript - Prinzipienreihe Wenn wir in der tägl...
Inhaltsverzeichnis Installieren Sie Docker unter ...
Inhaltsdetail-Tags: <h1>~<h6>Titel-Ta...
Inhaltsverzeichnis 1. Filtern, Zuordnen und Reduz...
1. Gehen Sie auf die offizielle Website www.mysql...
Dieser Artikel zeichnet das Installationstutorial...
Als ich kürzlich für einen Kunden druckte, bat er ...
Erstellen Sie eine Animation der acht Planeten de...
Inhaltsverzeichnis Vorwort 1. SS-Befehl 2. Gesamt...