Berechnen Sie die Zeilengröße und die maximale Zeilengröße für eine Tabelle

10

Problem:

Gibt es eine Möglichkeit, die Anzahl der von der Tabellenerstellung belegten Bytes zu berechnen? Ich weiß, dass Sie einige Informationen aus information_schema.tables abrufen können, diese Informationen jedoch nicht genau genug sind.

Was tatsächlich benötigt wird, ist die Anzahl der Bytes gemäß der Definition der Tabelle nur für innodb, und die Kollatierung könnte auch als utf-8-general-ci betrachtet werden

Zum Beispiel ist ein Tabellentest wie folgt

Tabellentest erstellen
(
col1 varchar (25),
col2 int,
col3 varchar (3),
col4 char (15),
col5 datetime
);

Jetzt müsste die Gesamtzeilengröße bekannt sein, die in einer Zeile entsprechend den Spaltentypen in der Tabelle akkumuliert werden kann.

Ich habe eine ähnliche Lösung in MSSQL gefunden, benötige aber die MySQL-Version

Skript zum Schätzen der Zeilengröße für jede Tabelle

Jede Hilfe wird sehr geschätzt.

Nawaz Sohail
quelle
Es kann von der Engine und dem Zeilenformat der Tabelle abhängen, sodass MySQL sie wahrscheinlich nirgendwo speichert (und sie möglicherweise sogar nicht kennt).
Jkavalik
Ich habe gerade einen Link zu genau dem hinzugefügt, wonach ich suche ... ja, aber es sollte eine Möglichkeit geben, eine Tabelle zu inspizieren und zu sagen, dass sie je nach Struktur so viele Bytes belegen würde
Nawaz Sohail

Antworten:

2

Nach langem Nachdenken und Nachforschen fand man eine Antwort, die wirklich dazu beitrug, das zu erreichen, was erforderlich war. Es ist ein Perl-Skript und Referenzlink ist

http://dev.mysql.com/doc/refman/5.6/en/storage-requirements.html

#!/usr/bin/perl
use strict;
$| = 1;

my %DataType = (
"TINYINT"=>1, "SMALLINT"=>2, "MEDIUMINT"=>3, "INT"=>4, "INTEGER"=>4, "BIGINT"=>8,
"FLOAT"=>'$M<=24?4:8', "DOUBLE"=>8,
"DECIMAL"=>'int(($M-$D)/9)*4+int(((($M-$D)%9)+1)/2)+int($D/9)*4+int((($D%9)+1)/2)',
"NUMERIC"=>'int(($M-$D)/9)*4+int(((($M-$D)%9)+1)/2)+int($D/9)*4+int((($D%9)+1)/2)',
"BIT"=>'($M+7)>>3',
"DATE"=>3, "TIME"=>3, "DATETIME"=>8, "TIMESTAMP"=>4, "YEAR"=>1,
"BINARY"=>'$M',"CHAR"=>'$M*$CL',
"VARBINARY"=>'$M+($M>255?2:1)', "VARCHAR"=>'$M*$CL+($M>255?2:1)',
"ENUM"=>'$M>255?2:1', "SET"=>'($M+7)>>3',
"TINYBLOB"=>9, "TINYTEXT"=>9,
"BLOB"=>10, "TEXT"=>10,
"MEDIUMBLOB"=>11, "MEDIUMTEXT"=>11,
"LONGBLOB"=>12, "LONGTEXT"=>12
);

my %DataTypeMin = (
"VARBINARY"=>'($M>255?2:1)', "VARCHAR"=>'($M>255?2:1)'
);

my ($D, $M, $S, $C, $L, $dt, $dp ,$bc, $CL);
my $fieldCount = 0;
my $byteCount = 0;
my $byteCountMin = 0;
my @fields = ();
my $fieldName;
my $tableName;
my $defaultDbCL = 1;
my $defaultTableCL = 1;
my %charsetMaxLen;
my %collationMaxLen;

open (CHARSETS, "mysql -B --skip-column-names information_schema -e 'select CHARACTER_SET_NAME,MAXLEN from CHARACTER_SETS;' |");
%charsetMaxLen = map ( ( /^(\w+)/ => /(\d+)$/ ), <CHARSETS>);
close CHARSETS;

open (COLLATIONS, "mysql -B --skip-column-names information_schema -e 'select COLLATION_NAME,MAXLEN from CHARACTER_SETS INNER JOIN COLLATIONS USING(CHARACTER_SET_NAME);' |");
%collationMaxLen = map ( ( /^(\w+)/ => /(\d+)$/ ), <COLLATIONS>);
close COLLATIONS;

open (TABLEINFO, "mysqldump -d --compact ".join(" ",@ARGV)." |");

while (<TABLEINFO>) {
chomp;
if ( ($S,$C) = /create database.*?`([^`]+)`.*default\scharacter\sset\s+(\w+)/i ) {
$defaultDbCL = exists $charsetMaxLen{$C} ? $charsetMaxLen{$C} : 1;
print "Database: $S".($C?" DEFAULT":"").($C?" CHARSET $C":"")." (bytes per char: $defaultDbCL)\n\n";
next;
}
if ( /^create table\s+`([^`]+)`.*/i ) {
$tableName = $1;
@fields = ();
next;
}
if ( $tableName && (($C,$L) = /^\)(?:.*?default\scharset=(\w+))?(?:.*?collate=(\w+))?/i) ) {
$defaultTableCL = exists $charsetMaxLen{$C} ? $charsetMaxLen{$C} : (exists $collationMaxLen{$L} ? $collationMaxLen{$L} : $defaultDbCL);
print "Table: $tableName".($C||$L?" DEFAULT":"").($C?" CHARSET $C":"").($L?" COLLATION $L":"")." (bytes per char: $defaultTableCL)\n";
$tableName = "";
$fieldCount = 0;
$byteCount = 0;
$byteCountMin = 0;
while ($_ = shift @fields) {
if ( ($fieldName,$dt,$dp,$M,$D,$S,$C,$L) = /\s\s`([^`]+)`\s+([a-z]+)(\((\d+)(?:,(\d+))?\)|\((.*)\))?(?:.*?character\sset\s+(\w+))?(?:.*?collate\s+(\w+))?/i ) {
$dt = uc $dt;
if (exists $DataType{$dt}) {
if (length $S) {
$M = ($S =~ s/(\'.*?\'(?!\')(?=,|$))/$1/g);
$dp = "($M : $S)"
}
$D = 0 if !$D;
$CL = exists $charsetMaxLen{$C} ? $charsetMaxLen{$C} : (exists $collationMaxLen{$L} ? $collationMaxLen{$L} : $defaultTableCL);
$bc = eval($DataType{$dt});
$byteCount += $bc;
$byteCountMin += exists $DataTypeMin{$dt} ? $DataTypeMin{$dt} : $bc;
} else {
$bc = "??";
}
$fieldName.="\t" if length($fieldName) < 8;
print "bytes:\t".$bc."\t$fieldName\t$dt$dp".($C?" $C":"").($L?" COLL $L":"")."\n";
++$fieldCount;
}
}
print "total:\t$byteCount".($byteCountMin!=$byteCount?"\tleast: $byteCountMin":"\t\t")."\tcolumns: $fieldCount\n\n";
next;
}
push @fields, $_;
}
close TABLEINFO;

Vielen Dank für die großartige Hilfe.

Nawaz Sohail
quelle
Ich erhalte keine Ausgabe, wenn ich dieses Skript ausführe. Was vermisse ich?
Kritischer
0

Sie müssen die Größe jedes Felds in Bytes gemäß dem Datentyp kennen ( MySQL-Referenz hier ) und diese Werte dann zusammenfassen.

dr01
quelle
3
Kann man nicht mit einer dynamischen Abfrage prüfen, welche Spalten von welcher Länge sind, und sie zusammenfassen? Deshalb habe ich darum gebeten. Wenn Sie sie teilen können, wäre dies eine große Hilfe
Nawaz Sohail,
0

Schritt 1:

col1 varchar(25),  2 + avg_byte_len
col2 int,          4
col4 char(15),     1*15 or 3*15 or...
col5 datetime      Pre-5.6: 8; then 5

SELECT AVG(LENGTH(col1)) as avg_byte_len,
       AVG(CHAR_LENGTH(col1) as avg_num_chars FROM ...;

20 englische Zeichen: 2 + 1 * 20
20 nahöstliche / slawische Zeichen: 2 + 2 * 20
20 asiatische Zeichen: 2 + 3 * 20
20 Emoji-Zeichen: 2 + 4 * 20 (und Sie benötigen utf8mb4)

Schritt 2: Addieren Sie diese.

Schritt 3: Multiplizieren Sie mit 2 bis 3, um den InnoDB-Overhead zu berücksichtigen. Ich habe festgestellt, dass dieser Faktor normalerweise funktioniert. (Aber nicht für winzige Tabellen und nicht unbedingt gut für partitionierte Tabellen.)

Ich sehe keinen Grund, die maximale Größe jeder Spalte anzunehmen.

Sie können näher als SHOW TABLE STATUSoder die entsprechenden information_schemaDaten kommen:

Schritt 1: SELECT COUNT(*)- uns dies anstelle vonRows

Schritt 2: Holen Sie sich Data_length + Index_length + Data_free

Schritt 3: Teilen.

Rick James
quelle
Vielen Dank für Ihre großartige Hilfe. Was aber, wenn eine Tabelle mehr als 100 Spalten mit varianten Datentypen enthält, wie würden wir dann die Schätzungen der Zeilengröße erhalten?
Nawaz Sohail
SELECT AVG(LENGTH(varchar_col))- Hinweis: LENGTHist bereits Bytes ; Keine Notwendigkeit, mit 2/3/4 zu multiplizieren. ( CHAR_LENGTHErhält die Länge in Zeichen.)
Rick James
0

Ich habe ein grobes Bash-Skript erstellt, um die Zeilengröße zu berechnen und zu warnen, wenn das auf dem Schema basierende Limit überschritten wird:

#!/bin/bash

#
# usage: mysqldump --no-data | check_row_size.sh
#

#
#
# https://dev.mysql.com/doc/refman/8.0/en/column-count-limit.html#row-size-limits
#
# The maximum row size for an InnoDB table, which applies to data stored locally within a database page, is slightly less than half a page for 4KB, 8KB, 16KB, and 32KB innodb_page_size settings.
# For example, the maximum row size is slightly less than 8KB for the default 16KB InnoDB page size.
#
#
# MariaDB [(none)]> show variables like 'innodb_page_size';
#+------------------+-------+
#| Variable_name    | Value |
#+------------------+-------+
#| innodb_page_size | 16384 |
#+------------------+-------+
#1 row in set (0.00 sec)
#
#
# Options:
# 1. Change default innodb_page_size to 32k
# 2. Change storage engine to DYNAMIC for tables
# 3. ?
#

#===========================================================================================
# Functions
#===========================================================================================
RETVAL=0

calc_row_size() {
    local -n TABLE_FIELDS=$1
    local -n TABLE_CHARSET=$2
    local FIELD_TYPE=""
    local FIELD_SIZE=""
    local FIELD=""
    local ROW_SIZE=0
    local IFS=$'|' # To split the vars using set
    for FIELD in "${TABLE_FIELDS[@]}"  
    do  
        set $FIELD
        FIELD_NAME=$1
        FIELD_TYPE=$2
        FIELD_SIZE=$3        
        calc_field_size_in_bytes $FIELD_TYPE $FIELD_SIZE $TABLE_CHARSET
        ROW_SIZE=$((ROW_SIZE + RETVAL))
        [ $DEBUG -gt 0 ] && echo "DEBUG1: Field name: $FIELD_NAME type: $FIELD_TYPE lenght: $FIELD_SIZE size: $RETVAL bytes Row size: $ROW_SIZE"
    done  
    RETVAL=$ROW_SIZE
}

calc_field_size_in_bytes() {
    local TYPE=$1
    local SIZE=$2
    local CHARSET=$3

    case $FIELD_TYPE in
        varchar)
            # https://adayinthelifeof.nl/2010/12/04/about-using-utf-8-fields-in-mysql/
            # Max 3 bytes per utf-8 chat in mysql
            case $CHARSET in
                utf8)
                    RETVAL=$((SIZE * 3))  # 3 bytes per character for utf8 
                ;;
                latin1)
                    RETVAL=$((SIZE))  # 1 byte per character for latin1
                ;;
                *)
                    echo "Unknown charset ($CHARSET), please fix the script"
                    exit 1
                ;;
            esac
        ;;
        smallint|int|bigint|tinyint|varbinary)
            RETVAL=$SIZE
        ;;
        blob)
            # https://dev.mysql.com/doc/refman/8.0/en/column-count-limit.html#row-size-limits
            # BLOB and TEXT columns only contribute 9 to 12 bytes toward the row size limit because their contents are stored separately from the rest of the row.
            RETVAL=9
        ;;
        text)
            RETVAL=12
        ;;
        timestamp)
            RETVAL=4 
        ;; 
        decimal)
            # https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html#data-types-storage-reqs-numeric
            # Each multiple of nine digits requires four bytes, and the leftover digits require some fraction of four bytes. 
            if [[ $SIZE =~ ([0-9]+),([0-9]+) ]] 
            then
              INTEGER_PART=${BASH_REMATCH[1]}
              FRACTIONAL_PART=${BASH_REMATCH[2]}

              INTEGER_BYTES=$((INTEGER_PART / 9 * 4))
              REMAINDER=$((INTEGER_PART % 9))
              case $REMAINDER in
                  0) INTEGER_BYTES=$((INTEGER_BYTES + 0)); ;;
                  1) INTEGER_BYTES=$((INTEGER_BYTES + 1)); ;;
                  2) INTEGER_BYTES=$((INTEGER_BYTES + 1)); ;;
                  3) INTEGER_BYTES=$((INTEGER_BYTES + 2)); ;;
                  4) INTEGER_BYTES=$((INTEGER_BYTES + 2)); ;;
                  5) INTEGER_BYTES=$((INTEGER_BYTES + 3)); ;;
                  6) INTEGER_BYTES=$((INTEGER_BYTES + 3)); ;;
                  7) INTEGER_BYTES=$((INTEGER_BYTES + 4)); ;;
                  8) INTEGER_BYTES=$((INTEGER_BYTES + 4)); ;;
              esac

              FRACTIONAL_BYTES=$((FRACTIONAL_PART / 9 * 4))
              REMAINDER=$((FRACTIONAL_PART % 9))
              case $REMAINDER in
                  0) FRACTIONAL_BYTES=$((FRACTIONAL_BYTES + 0)); ;;
                  1) FRACTIONAL_BYTES=$((FRACTIONAL_BYTES + 1)); ;;
                  2) FRACTIONAL_BYTES=$((FRACTIONAL_BYTES + 1)); ;;
                  3) FRACTIONAL_BYTES=$((FRACTIONAL_BYTES + 2)); ;;
                  4) FRACTIONAL_BYTES=$((FRACTIONAL_BYTES + 2)); ;;
                  5) FRACTIONAL_BYTES=$((FRACTIONAL_BYTES + 3)); ;;
                  6) FRACTIONAL_BYTES=$((FRACTIONAL_BYTES + 3)); ;;
                  7) FRACTIONAL_BYTES=$((FRACTIONAL_BYTES + 4)); ;;
                  8) FRACTIONAL_BYTES=$((FRACTIONAL_BYTES + 4)); ;;
              esac
              [ $DEBUG -gt 0 ] && echo "DEBUG1: Calulation of decimal: SIZE: $SIZE INTEGER_PART:$INTEGER_PART FRACTIONAL_PART:$FRACTIONAL_PART TOTAL = INTEGER_BYTES($INTEGER_BYTES) + FRACTIONAL_BYTES($FRACTIONAL_BYTES)"
              RETVAL=$((INTEGER_BYTES + FRACTIONAL_BYTES)) 
            else
                echo "Seems like SIZE ($SIZE) for a decimal field doesn't match pattern ([0-9]+),([0-9]+). Please investigate"
                exit 1
            fi
        ;;
        *)
            echo "Found a field type that is not handled: $TYPE. Please fix before proceeding."
            exit 1
        ;;
    esac
}


#===========================================================================================
# INIT
#===========================================================================================
INSIDE_CREATE_TABLE_STATEMENT=false # True if we are within a create table statement
TABLE_NAME=''  # Current table name
ROW_SIZE=0 # Current row size being calculated
DEBUG=0
VERBOSE=0
MAX_SIZE=8126 # Default
declare -a FIELDS # List of fields from the current CREATE TABLE statement

#===========================================================================================
# Parameters
#===========================================================================================
OPTIND=1         # Reset in case getopts has been used previously in the shell.

while getopts "hvdt:" opt; do
    case "$opt" in
    h)
        echo "Usage: mysqldump --no-data | ./check_row_size [-v|-d] [-t threshold]"
        exit 0
        ;;
    v) VERBOSE=1
        ;;
    d) DEBUG=2
        ;;
    t) MAX_SIZE=$OPTARG
        ;;
    esac
done


#===========================================================================================
# MAIN Loop - parses schema then calc row_size based on charset
#===========================================================================================
while IFS= read -r LINE
do
    [ $DEBUG -gt 1 ] && echo "DEBUG2: Read: $LINE"
    # Are we within a CREATE TABLE statement?
    if [ $INSIDE_CREATE_TABLE_STATEMENT == "false" ]
    then
        # Nope, is the current line a 'CREATE TABLE' statement?
        if [[ $LINE =~ ^"CREATE TABLE \`"([^\`]+) ]] 
        then
            [ $DEBUG -gt 0 ] && echo "CREATE TABLE FOUND!: $TABLE_NAME"
            TABLE_NAME=${BASH_REMATCH[1]} # What has been caught between pattern parenthesis
            INSIDE_CREATE_TABLE_STATEMENT='true'
            FIELDS=()
        fi
        continue # Ok, next line 
    fi
    # Is this a create table field definition line?
    if [[ $LINE =~ ^' '+'`'([^'`']+)'` '([a-z]+)'('([^')']+) ]]
    then
        FIELD_NAME=${BASH_REMATCH[1]}
        FIELD_TYPE=${BASH_REMATCH[2]}
        FIELD_SIZE=${BASH_REMATCH[3]}
        FIELDS+=( "$FIELD_NAME|$FIELD_TYPE|$FIELD_SIZE" )
        continue
    fi
    # Have we reached the end of the CREATE TABLE statement?
    if [[ $LINE =~ ^") ENGINE=InnoDB DEFAULT CHARSET="([^ ]+) ]] 
    then
        CHARSET=${BASH_REMATCH[1]}
        [ $DEBUG -gt 0 ] && echo "End of CREATE TABLE statement"
        calc_row_size FIELDS CHARSET
        ROW_SIZE=$RETVAL
        if [ $ROW_SIZE -gt $MAX_SIZE ]
        then
            echo "Table: $TABLE_NAME has a row size: $ROW_SIZE Bytes > $MAX_SIZE Bytes Charset: $CHARSET"
            # and is going to cause problem if the we upgrade to tables in ROW_FORMAT compact. See https://mariadb.com/kb/en/library/troubleshooting-row-size-too-large-errors-with-innodb/ for more details."
        fi
        INSIDE_CREATE_TABLE_STATEMENT='false'
    fi
done 

.

kritisch
quelle
-1

Es gibt bereits einige Fragen dieses Typs, zum Beispiel diese: Wie man die Datengröße und die Indexgröße einer Tabelle in MySQL schätzt / vorhersagt

Ein Unterschied zwischen dieser Frage und Ihrer Tabelle ist das Vorhandensein von Zeichenfolgen variabler Länge in Ihrer - denken Sie daran, die maximale Größe zu berücksichtigen, die sie haben können.

Denken Sie auch daran, dass ab Version 5 varchar(25)bis zu 25 Zeichen und nicht mehr als 25 Byte vorhanden sind. Wenn Sie also wahrscheinlich Nicht-ASCII-Zeichen in Ihren Zeichenfolgen sehen, kann die Spaltengröße maximal 100 Byte betragen, da einige Zeichen vier Byte benötigen zu repräsentieren - zum Beispiel "Haufen von Poo Emoji" (was, ich scherze nicht, existiert - wenn Ihr aktueller Browser + Schriftart es unterstützt, sieht es so aus: 💩) ist 0xF0 0x9F 0x92 0xA9. Vor v5 zählte mySQL bei der Angabe der Länge des Zeichenfolgentyps Bytes und keine Zeichen.

Bearbeiten bezüglich Automatisierung

Um den Prozess zu automatisieren, sollten Sie in der Lage sein, alle erforderlichen Informationen aus den INFORMATION_SCHEMATabellen auf ähnliche Weise wie das für MS SQL Server gefundene Skript abzuleiten . Unter https://dev.mysql.com/doc/refman/5.0/en/information-schema.html finden Sie eine Dokumentation dazu.

David Spillett
quelle
Ich habe die gemeinsame Antwort bereits vor dem Posten dieser Frage gesehen. Das gesuchte Objekt muss nicht auf information_schema.tables verweisen, da es möglicherweise nicht genau ist, sondern eine Lösung, die die Tabellenstruktur überprüft und mir die entsprechende Zeilengröße gibt.
Nawaz Sohail
Sie könnten ohne Zweifel eine mySQL-Version des Skripts erstellen, das Sie bereits gefunden haben. Die INFORMATION_SCHEMATabellen sollten die benötigten Informationen enthalten. Eine Dokumentation dazu finden Sie unter dev.mysql.com/doc/refman/5.0/de/information-schema.html .
David Spillett
@DavidSpillett Die Operation fragt nach der maximalen Zeilengröße. Das Informationsschema liefert nur Informationen über die durchschnittliche Zeilengröße.
Kritischer