問題:
テーブルの作成に使用されるバイト数を計算する方法はありますか?information_schema.tablesからいくつかの情報を取得できますが、その情報は十分に正確ではありません。
実際に必要なのは、innodbのみのテーブルの定義によるバイト数であり、照合はutf-8-general-ci
たとえば、テーブルテストは次のようになります。
テーブルテストを作成する
(
col1 varchar(25)、
col2 int、
col3 varchar(3)、
col4 char(15)、
col5日時
);
テーブルの列のタイプに応じて1つの行に累積できる合計行サイズを知る必要があります。
MSSQLで同様のソリューションを見つけたが、MySQLバージョンが必要
どんな助けでも大歓迎です。
多くの考えと調査の結果、必要なものを達成するのに役立つ1つの答えが見つかりました。これは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;
多大なご協力ありがとうございました。
大まかなbashスクリプトを作成して行サイズを計算し、スキーマに基づく制限を超えた場合に警告します。
#!/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
データタイプ( MySQLリファレンスはこちら )に従って、各フィールドのバイト単位のサイズを把握し、これらの値を合計する必要があります。
このタイプのいくつかの質問はすでにあります。たとえば、これは次のとおりです。 MySQLのテーブルのデータサイズとインデックスサイズを推定/予測する方法
その質問とあなたのテーブルの1つの違いは、あなたの中に可変長文字列が存在することです-それらが持つことができる最大サイズを考慮することを忘れないでください。
また、バージョン5以降では、varchar(25)
は最大25文字ではなく最大25であることに注意してください。 )bytes文字列に非ASCII文字が表示される可能性が高い場合、一部の文字は4バイトかかるため、列のサイズは最大100バイトまで膨らむ可能性があります。 of poo emoji "(これは存在しないと思いますが、現在のブラウザとフォントがサポートしている場合は????)は0xF0 0x9F 0x92 0xA9です。 v5より前のバージョンでは、mySQLは文字列型の長さを指定するときに文字ではなくバイトをカウントしていました。
自動化に関する編集
プロセスの自動化に関しては、MS SQL Serverで見つけたスクリプトと同様の方法で、INFORMATION_SCHEMA
テーブルから必要なすべての情報を導出できるはずです。それをカバーするいくつかのドキュメントについては https://dev.mysql.com/doc/refman/5.0/en/information-schema.html を参照してください。
ステップ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文字:2 + 4 * 20(および_utf8mb4
_が必要)
ステップ2:それらを追加します。
ステップ3:InnoDBのオーバーヘッドを考慮して、2と3の間のいずれかを掛けます。その要因通常が機能することを発見しました。 (しかし、小さなテーブルではなく、パーティション分割されたテーブルでは必ずしも適切ではありません。)
各列の最大サイズを採用する理由はわかりません。
_SHOW TABLE STATUS
_または同等の_information_schema
_データより近づくことができます。
ステップ1:SELECT COUNT(*)
-Rows
の代わりにこれを使用します
ステップ2:_Data_length + Index_length + Data_free
_を取得する
ステップ3:分割します。