คำนวณขนาดแถวและขนาดแถวสูงสุดสำหรับตาราง


10

ปัญหา:

มีวิธีคำนวณจำนวนไบต์ที่ใช้โดยการสร้างตารางหรือไม่ฉันรู้ว่าคุณสามารถรับข้อมูลบางอย่างจาก data_schema.tables แต่ข้อมูลนั้นไม่ถูกต้องเพียงพอ

สิ่งที่จำเป็นจริง ๆ คือจำนวนไบต์ตามคำจำกัดความของตารางสำหรับinnodb เท่านั้นและการเปรียบเทียบอาจพิจารณาเป็นutf-8-general-ci

ตัวอย่างเช่นการทดสอบตารางมีดังนี้

สร้างการทดสอบตาราง
(
col1 varchar (25),
col2 int,
col3 varchar (3),
col4 ถ่าน (15),
col5 datetime
);

ตอนนี้จะต้องทราบขนาดแถวทั้งหมดที่สามารถสะสมในหนึ่งแถวตามประเภทของคอลัมน์ในตาราง

พบวิธีการแก้ปัญหาที่คล้ายกันใน MSSQL แต่ต้องการรุ่น MySQL ของมัน

สคริปต์เพื่อประมาณขนาดแถวสำหรับตารางใด ๆ

ความช่วยเหลือใด ๆ ที่ชื่นชมมาก


มันขึ้นอยู่กับเครื่องยนต์และรูปแบบแถวของตารางดังนั้น MySQL อาจไม่เก็บไว้ที่ใดก็ได้ (และอาจไม่รู้ด้วยซ้ำ)
jkavalik

เพิ่งเพิ่มลิงก์ไปยังสิ่งที่ฉันกำลังมองหา ... ใช่ แต่ควรมีวิธีตรวจสอบตารางและบอกว่ามันจะใช้จำนวนไบต์ที่มากขึ้นอยู่กับโครงสร้างของมัน
Nawaz Sohail

คำตอบ:


2

หลังจากคิดมากและค้นคว้าพบคำตอบเดียวซึ่งช่วยในการบรรลุสิ่งที่ต้องการจริงๆ มันเป็นสคริปต์ Perl และลิงค์อ้างอิงคือ

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;

ขอบคุณทุกคนสำหรับความช่วยเหลือที่ดี


ฉันไม่ได้รับผลลัพธ์ใด ๆ เมื่อเรียกใช้สคริปต์ ฉันกำลังคิดถึงอะไร
srcritical

เพิ่ม-uUser -pPassคำสั่ง mysql และ mysqldump ในสคริปต์ (หรือลอง--defaults-extra-file=/etc/mysql/debian.cnfแทน Ubuntu / Debian) และเรียกใช้กับฐานข้อมูลเป็นอาร์กิวเมนต์แรกเช่นperl test.pl mydatabase
dw1

0

คุณจำเป็นต้องรู้ขนาดเป็นไบต์ของแต่ละฟิลด์ตามชนิดข้อมูล ( การอ้างอิง MySQL ที่นี่ ) จากนั้นสรุปค่าเหล่านี้เข้าด้วยกัน


3
ไม่สามารถทำได้ด้วยการใช้แบบสอบถามแบบไดนามิกเพื่อตรวจสอบว่าคอลัมน์ใดมีความยาวและสรุปรวมหรือไม่ .. นั่นเป็นสาเหตุที่ฉันขอให้คุณ .. หากคุณสามารถแบ่งปันมันจะเป็นความช่วยเหลือที่ดี
Nawaz Sohail

0

ขั้นตอนที่ 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 ตัวอักษรภาษาอังกฤษ: 2 + 1 * 20
20 ตัวอักษรกลางตะวันออก / สลาฟ: 2 + 2 * 20
20 ตัวละครเอเชีย: 2 + 3 * 20
20 ตัวอักษร Emoji: 2 + 4 * 20 (และคุณต้องการutf8mb4)

ขั้นตอนที่ 2: เพิ่มสิ่งเหล่านั้น

ขั้นตอนที่ 3: คูณด้วยจำนวนใดก็ได้ระหว่าง 2 ถึง 3 เพื่อให้สามารถใช้งาน InnoDB ได้ ฉันพบว่าปัจจัยดังกล่าวมักใช้งานได้ (แต่ไม่ใช่สำหรับโต๊ะเล็ก ๆ และไม่จำเป็นสำหรับโต๊ะที่แบ่งพาร์ติชัน)

ฉันไม่เห็นเหตุผลที่จะใช้ขนาดสูงสุดของแต่ละคอลัมน์

คุณสามารถเข้าใกล้กว่าSHOW TABLE STATUSหรือinformation_schemaข้อมูลที่เทียบเท่า:

ขั้นตอนที่ 1: SELECT COUNT(*)- เราใช้แทนRows

ขั้นตอนที่ 2: รับ Data_length + Index_length + Data_free

ขั้นตอนที่ 3: แบ่ง


ขอบคุณสำหรับความช่วยเหลือที่ดีของคุณ แต่ถ้าตารางมีคอลัมน์มากกว่า 100 คอลัมน์ที่มีประเภทข้อมูลที่แตกต่างกันเราจะได้ขนาดประมาณแถวอย่างไร
Nawaz Sohail

SELECT AVG(LENGTH(varchar_col))- หมายเหตุ: LENGTHมีอยู่แล้วไบต์ ; ไม่จำเป็นต้องคูณด้วย 2/3/4 ( CHAR_LENGTHเพิ่มความยาวเป็นตัวอักษร)
Rick James

0

ฉันได้สร้างสคริปต์ทุบตีคร่าวๆเพื่อคำนวณขนาดของแถวและเตือนว่าผ่านขีด จำกัด ตามสคีมาหรือไม่:

#!/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 


-1

มีคำถามสองสามข้อที่เป็นประเภทนี้อยู่แล้วตัวอย่างเช่นคำถามนี้มีวิธีการประมาณ / ทำนายขนาดข้อมูลและขนาดดัชนีของตารางใน MySQL

ข้อแตกต่างอย่างหนึ่งระหว่างคำถามนั้นกับตารางของคุณคือการมีสตริงความยาวแปรผันในของคุณ - อย่าลืมคำนึงถึงขนาดสูงสุดที่สามารถเป็นได้

โปรดจำไว้ว่าตั้งแต่เวอร์ชัน 5 เป็นต้นไปที่varchar(25)มีความยาวสูงสุด 25 อักขระไม่เกิน 25 ไบต์ดังนั้นหากคุณมีแนวโน้มที่จะเห็นอักขระที่ไม่ใช่ ASCII ในสตริงของคุณขนาดคอลัมน์อาจมีบอลลูนสูงสุด 100 ไบต์เนื่องจากอักขระบางตัวใช้เวลาสี่ไบต์ เพื่อเป็นตัวแทน - ตัวอย่างเช่น "pile of poo emoji" (ซึ่งฉันล้อเล่นไม่ได้มีอยู่ - ถ้าเบราว์เซอร์ + แบบอักษรปัจจุบันของคุณรองรับมันดูเหมือนว่า: 💩) คือ 0xF0 0x9F 0x92 0xA9 ก่อนหน้า v5 mySQL จะนับไบต์ไม่ใช่ตัวอักษรเมื่อระบุความยาวประเภทสตริง

แก้ไขเกี่ยวกับระบบอัตโนมัติ

ในแง่ของกระบวนการอัตโนมัติคุณควรจะได้รับข้อมูลทั้งหมดที่ต้องการจากINFORMATION_SCHEMAตารางในลักษณะที่คล้ายคลึงกับสคริปต์ที่คุณพบสำหรับ MS SQL Server ดูhttps://dev.mysql.com/doc/refman/5.0/en/information-schema.htmlสำหรับเอกสารบางอย่างที่ครอบคลุม


ฉันเห็นคำตอบที่ใช้ร่วมกันแล้วก่อนที่จะโพสต์คำถามนี้สิ่งที่ฉันกำลังมองหาไม่จำเป็นต้องชี้ information_schema.tables เนื่องจากอาจไม่ถูกต้อง แต่เป็นวิธีการแก้ปัญหาที่อาจตรวจสอบโครงสร้างตารางและให้ขนาดแถวตามฉัน
Nawaz Sohail

คุณไม่ต้องสงสัยเลยว่าสร้างสคริปต์เวอร์ชัน mySQL ที่คุณพบแล้ว INFORMATION_SCHEMAตารางควรจะรวมถึงข้อมูลที่คุณต้องการ ดูที่dev.mysql.com/doc/refman/5.0/en/information-schema.htmlสำหรับเอกสารบางอย่างที่ครอบคลุม
David Spillett

@DavidSpillett op จะถามเกี่ยวกับขนาดแถวสูงสุด schema ข้อมูลให้เฉพาะขนาดแถวเฉลี่ย aobut ข้อมูล
srcritical
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.