提交 ab3bb9cf 编写于 作者: T Tom Lane

Add some real documentation about TOAST (finally). Combine this with

the old 'page' chapter and the recently added 'filelayout' chapter to
make a coherent chapter about PostgreSQL's physical storage layout.
上级 521e8888
<!--
$PostgreSQL: pgsql/doc/src/sgml/diskusage.sgml,v 1.13 2004/12/28 19:08:58 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/diskusage.sgml,v 1.14 2005/01/10 00:04:38 tgl Exp $
-->
<chapter id="diskusage">
......@@ -22,12 +22,12 @@ $PostgreSQL: pgsql/doc/src/sgml/diskusage.sgml,v 1.13 2004/12/28 19:08:58 tgl Ex
stored. If the table has any columns with potentially-wide values,
there is also a <acronym>TOAST</> file associated with the table,
which is used to store values too wide to fit comfortably in the main
table. There will be one index on the
table (see <xref linkend="storage-toast">). There will be one index on the
<acronym>TOAST</> table, if present. There may also be indexes associated
with the base table. Each table and index is stored in a separate disk
file &mdash; possibly more than one file, if the file would exceed one
gigabyte. Naming conventions for these files are described in <xref
linkend="file-layout">.
linkend="storage-file-layout">.
</para>
<para>
......
<!--
$PostgreSQL: pgsql/doc/src/sgml/filelayout.sgml,v 1.2 2004/11/16 15:00:36 tgl Exp $
-->
<chapter id="file-layout">
<title>Database File Layout</title>
<abstract>
<para>
A description of the database physical storage layout.
</para>
</abstract>
<para>
This section provides an overview of the physical format used by
<productname>PostgreSQL</productname> databases.
</para>
<para>
All the data needed for a database cluster is stored within the cluster's data
directory, commonly referred to as <varname>PGDATA</> (after the name of the
environment variable that can be used to define it). A common location for
<varname>PGDATA</> is <filename>/var/lib/pgsql/data</>. Multiple clusters,
managed by different postmasters, can exist on the same machine.
</para>
<para>
The <varname>PGDATA</> directory contains several subdirectories and control
files, as shown in <xref linkend="pgdata-contents-table">. In addition to
these required items, the cluster configuration files
<filename>postgresql.conf</filename>, <filename>pg_hba.conf</filename>, and
<filename>pg_ident.conf</filename> are traditionally stored in
<varname>PGDATA</> (although beginning in
<productname>PostgreSQL</productname> 8.0 it is possible to keep them
elsewhere).
</para>
<table tocentry="1" id="pgdata-contents-table">
<title>Contents of <varname>PGDATA</></title>
<tgroup cols="2">
<thead>
<row>
<entry>
Item
</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><filename>PG_VERSION</></entry>
<entry>A file containing the major version number of <productname>PostgreSQL</productname></entry>
</row>
<row>
<entry><filename>base</></entry>
<entry>Subdirectory containing per-database subdirectories</entry>
</row>
<row>
<entry><filename>global</></entry>
<entry>Subdirectory containing cluster-wide tables, such as
<structname>pg_database</></entry>
</row>
<row>
<entry><filename>pg_clog</></entry>
<entry>Subdirectory containing transaction commit status data</entry>
</row>
<row>
<entry><filename>pg_subtrans</></entry>
<entry>Subdirectory containing subtransaction status data</entry>
</row>
<row>
<entry><filename>pg_tblspc</></entry>
<entry>Subdirectory containing symbolic links to tablespaces</entry>
</row>
<row>
<entry><filename>pg_xlog</></entry>
<entry>Subdirectory containing WAL (Write Ahead Log) files</entry>
</row>
<row>
<entry><filename>postmaster.opts</></entry>
<entry>A file recording the command-line options the postmaster was
last started with</entry>
</row>
<row>
<entry><filename>postmaster.pid</></entry>
<entry>A lock file recording the current postmaster PID and shared memory
segment ID (not present after postmaster shutdown)</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
For each database in the cluster there is a subdirectory within
<varname>PGDATA</><filename>/base</>, named after the database's OID in
<structname>pg_database</>. This subdirectory is the default location
for the database's files; in particular, its system catalogs are stored
there.
</para>
<para>
Each table and index is stored in a separate file, named after the table
or index's <firstterm>filenode</> number, which can be found in
<structname>pg_class</>.<structfield>relfilenode</>.
</para>
<caution>
<para>
Note that while a table's filenode often matches its OID, this is
<emphasis>not</> necessarily the case; some operations, like
<command>TRUNCATE</>, <command>REINDEX</>, <command>CLUSTER</> and some forms
of <command>ALTER TABLE</>, can change the filenode while preserving the OID.
Avoid assuming that filenode and table OID are the same.
</para>
</caution>
<para>
When a table or index exceeds 1Gb, it is divided into gigabyte-sized
<firstterm>segments</>. The first segment's file name is the same as the
filenode; subsequent segments are named filenode.1, filenode.2, etc.
This arrangement avoids problems on platforms that have file size limitations.
The contents of tables and indexes are discussed further in
<xref linkend="page">.
</para>
<para>
A table that has columns with potentially large entries will have an
associated <firstterm>TOAST</> table, which is used for out-of-line storage of
field values that are too large to keep in the table rows proper.
<structname>pg_class</>.<structfield>reltoastrelid</> links from a table to
its TOAST table, if any.
</para>
<para>
Tablespaces make the scenario more complicated. Each user-defined tablespace
has a symbolic link inside the <varname>PGDATA</><filename>/pg_tblspc</>
directory, which points to the physical tablespace directory (as specified in
its <command>CREATE TABLESPACE</> command). The symbolic link is named after
the tablespace's OID. Inside the physical tablespace directory there is
a subdirectory for each database that has elements in the tablespace, named
after the database's OID. Tables within that directory follow the filenode
naming scheme. The <literal>pg_default</> tablespace is not accessed through
<filename>pg_tblspc</>, but corresponds to
<varname>PGDATA</><filename>/base</>. Similarly, the <literal>pg_global</>
tablespace is not accessed through <filename>pg_tblspc</>, but corresponds to
<varname>PGDATA</><filename>/global</>.
</para>
</chapter>
<!-- $PostgreSQL: pgsql/doc/src/sgml/filelist.sgml,v 1.40 2004/12/03 05:50:18 momjian Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/filelist.sgml,v 1.41 2005/01/10 00:04:38 tgl Exp $ -->
<!entity history SYSTEM "history.sgml">
<!entity info SYSTEM "info.sgml">
......@@ -75,15 +75,14 @@
<!entity arch-dev SYSTEM "arch-dev.sgml">
<!entity bki SYSTEM "bki.sgml">
<!entity catalogs SYSTEM "catalogs.sgml">
<!entity filelayout SYSTEM "filelayout.sgml">
<!entity geqo SYSTEM "geqo.sgml">
<!entity gist SYSTEM "gist.sgml">
<!entity indexcost SYSTEM "indexcost.sgml">
<!entity nls SYSTEM "nls.sgml">
<!entity page SYSTEM "page.sgml">
<!entity plhandler SYSTEM "plhandler.sgml">
<!entity protocol SYSTEM "protocol.sgml">
<!entity sources SYSTEM "sources.sgml">
<!entity storage SYSTEM "storage.sgml">
<!-- appendixes -->
<!entity contacts SYSTEM "contacts.sgml">
......
<!--
$PostgreSQL: pgsql/doc/src/sgml/lobj.sgml,v 1.35 2005/01/08 22:13:33 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/lobj.sgml,v 1.36 2005/01/10 00:04:38 tgl Exp $
-->
<chapter id="largeObjects">
......@@ -51,9 +51,11 @@ $PostgreSQL: pgsql/doc/src/sgml/lobj.sgml,v 1.35 2005/01/08 22:13:33 tgl Exp $
</para>
<para>
<indexterm><primary>TOAST</></>
<indexterm><primary>sliced bread</><see>TOAST</></indexterm>
<productname>PostgreSQL 7.1</productname> introduced a mechanism
<indexterm>
<primary>TOAST</primary>
<secondary>versus large objects</secondary>
</indexterm>
<productname>PostgreSQL</productname> 7.1 introduced a mechanism
(nicknamed <quote><acronym>TOAST</acronym></quote>) that allows
data values to be much larger than single pages. This
makes the large object facility partially obsolete. One
......
<!--
$PostgreSQL: pgsql/doc/src/sgml/postgres.sgml,v 1.72 2004/12/30 03:13:56 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/postgres.sgml,v 1.73 2005/01/10 00:04:38 tgl Exp $
-->
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V4.2//EN" [
......@@ -237,8 +237,7 @@ $PostgreSQL: pgsql/doc/src/sgml/postgres.sgml,v 1.72 2004/12/30 03:13:56 tgl Exp
&geqo;
&indexcost;
&gist;
&filelayout;
&page;
&storage;
&bki;
</part>
......
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.75 2005/01/04 00:39:53 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.76 2005/01/10 00:04:43 tgl Exp $
PostgreSQL documentation
-->
......@@ -153,10 +153,14 @@ where <replaceable class="PARAMETER">action</replaceable> is one of:
inline, uncompressed. <literal>MAIN</literal> is for inline,
compressible data. <literal>EXTERNAL</literal> is for external,
uncompressed data, and <literal>EXTENDED</literal> is for external,
compressed data. <literal>EXTENDED</literal> is the default for all
data types that support it. Use of <literal>EXTERNAL</literal> will
compressed data. <literal>EXTENDED</literal> is the default for most
data types that support non-<literal>PLAIN</literal> storage.
Use of <literal>EXTERNAL</literal> will
make substring operations on <type>text</type> and <type>bytea</type>
columns faster, at the penalty of increased storage space.
columns faster, at the penalty of increased storage space. Note that
<literal>SET STORAGE</> doesn't itself change anything in the table,
it just sets the strategy to be pursued during future table updates.
See <xref linkend="storage-toast"> for more information.
</para>
</listitem>
</varlistentry>
......
<!--
$PostgreSQL: pgsql/doc/src/sgml/page.sgml,v 1.19 2004/11/12 21:50:53 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/storage.sgml,v 1.4 2005/01/10 00:04:38 tgl Exp $
-->
<chapter id="page">
<chapter id="storage">
<title>Database Page Layout</title>
<title>Database Physical Storage</title>
<para>
This chapter provides an overview of the physical storage format used by
<productname>PostgreSQL</productname> databases.
</para>
<sect1 id="storage-file-layout">
<title>Database File Layout</title>
<para>
This section describes the storage format at the level of files and
directories.
</para>
<para>
All the data needed for a database cluster is stored within the cluster's data
directory, commonly referred to as <varname>PGDATA</> (after the name of the
environment variable that can be used to define it). A common location for
<varname>PGDATA</> is <filename>/var/lib/pgsql/data</>. Multiple clusters,
managed by different postmasters, can exist on the same machine.
</para>
<para>
The <varname>PGDATA</> directory contains several subdirectories and control
files, as shown in <xref linkend="pgdata-contents-table">. In addition to
these required items, the cluster configuration files
<filename>postgresql.conf</filename>, <filename>pg_hba.conf</filename>, and
<filename>pg_ident.conf</filename> are traditionally stored in
<varname>PGDATA</> (although beginning in
<productname>PostgreSQL</productname> 8.0 it is possible to keep them
elsewhere).
</para>
<table tocentry="1" id="pgdata-contents-table">
<title>Contents of <varname>PGDATA</></title>
<tgroup cols="2">
<thead>
<row>
<entry>
Item
</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><filename>PG_VERSION</></entry>
<entry>A file containing the major version number of <productname>PostgreSQL</productname></entry>
</row>
<row>
<entry><filename>base</></entry>
<entry>Subdirectory containing per-database subdirectories</entry>
</row>
<row>
<entry><filename>global</></entry>
<entry>Subdirectory containing cluster-wide tables, such as
<structname>pg_database</></entry>
</row>
<row>
<entry><filename>pg_clog</></entry>
<entry>Subdirectory containing transaction commit status data</entry>
</row>
<row>
<entry><filename>pg_subtrans</></entry>
<entry>Subdirectory containing subtransaction status data</entry>
</row>
<row>
<entry><filename>pg_tblspc</></entry>
<entry>Subdirectory containing symbolic links to tablespaces</entry>
</row>
<row>
<entry><filename>pg_xlog</></entry>
<entry>Subdirectory containing WAL (Write Ahead Log) files</entry>
</row>
<row>
<entry><filename>postmaster.opts</></entry>
<entry>A file recording the command-line options the postmaster was
last started with</entry>
</row>
<row>
<entry><filename>postmaster.pid</></entry>
<entry>A lock file recording the current postmaster PID and shared memory
segment ID (not present after postmaster shutdown)</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
For each database in the cluster there is a subdirectory within
<varname>PGDATA</><filename>/base</>, named after the database's OID in
<structname>pg_database</>. This subdirectory is the default location
for the database's files; in particular, its system catalogs are stored
there.
</para>
<para>
Each table and index is stored in a separate file, named after the table
or index's <firstterm>filenode</> number, which can be found in
<structname>pg_class</>.<structfield>relfilenode</>.
</para>
<caution>
<para>
Note that while a table's filenode often matches its OID, this is
<emphasis>not</> necessarily the case; some operations, like
<command>TRUNCATE</>, <command>REINDEX</>, <command>CLUSTER</> and some forms
of <command>ALTER TABLE</>, can change the filenode while preserving the OID.
Avoid assuming that filenode and table OID are the same.
</para>
</caution>
<para>
When a table or index exceeds 1Gb, it is divided into gigabyte-sized
<firstterm>segments</>. The first segment's file name is the same as the
filenode; subsequent segments are named filenode.1, filenode.2, etc.
This arrangement avoids problems on platforms that have file size limitations.
The contents of tables and indexes are discussed further in
<xref linkend="storage-page-layout">.
</para>
<para>
A table that has columns with potentially large entries will have an
associated <firstterm>TOAST</> table, which is used for out-of-line storage of
field values that are too large to keep in the table rows proper.
<structname>pg_class</>.<structfield>reltoastrelid</> links from a table to
its <acronym>TOAST</> table, if any.
See <xref linkend="storage-toast"> for more information.
</para>
<para>
Tablespaces make the scenario more complicated. Each user-defined tablespace
has a symbolic link inside the <varname>PGDATA</><filename>/pg_tblspc</>
directory, which points to the physical tablespace directory (as specified in
its <command>CREATE TABLESPACE</> command). The symbolic link is named after
the tablespace's OID. Inside the physical tablespace directory there is
a subdirectory for each database that has elements in the tablespace, named
after the database's OID. Tables within that directory follow the filenode
naming scheme. The <literal>pg_default</> tablespace is not accessed through
<filename>pg_tblspc</>, but corresponds to
<varname>PGDATA</><filename>/base</>. Similarly, the <literal>pg_global</>
tablespace is not accessed through <filename>pg_tblspc</>, but corresponds to
<varname>PGDATA</><filename>/global</>.
</para>
</sect1>
<sect1 id="storage-toast">
<title>TOAST</title>
<indexterm>
<primary>TOAST</primary>
</indexterm>
<indexterm><primary>sliced bread</><see>TOAST</></indexterm>
<para>
This section provides an overview of <acronym>TOAST</> (The
Oversized-Attribute Storage Technique).
</para>
<para>
Since <productname>PostgreSQL</productname> uses a fixed page size (commonly
8Kb), and does not allow tuples to span multiple pages, it's not possible to
store very large field values directly. Before <productname>PostgreSQL</> 7.1
there was a hard limit of just under one page on the total amount of data that
could be put into a table row. In release 7.1 and later, this limit is
overcome by allowing large field values to be compressed and/or broken up into
multiple physical rows. This happens transparently to the user, with only
small impact on most of the backend code. The technique is affectionately
known as <acronym>TOAST</> (or <quote>the best thing since sliced bread</>).
</para>
<abstract>
<para>
A description of the database file page format.
Only certain data types support <acronym>TOAST</> &mdash; there is no need to
impose the overhead on data types that cannot produce large field values.
To support <acronym>TOAST</>, a data type must have a variable-length
(<firstterm>varlena</>) representation, in which the first 32-bit word of any
stored value contains the total length of the value in bytes (including
itself). <acronym>TOAST</> does not constrain the rest of the representation.
All the C-level functions supporting a <acronym>TOAST</>-able data type must
be careful to handle <acronym>TOAST</>ed input values. (This is normally done
by invoking <function>PG_DETOAST_DATUM</> before doing anything with an input
value; but in some cases more efficient approaches are possible.)
</para>
</abstract>
<para>
This section provides an overview of the page format used by
<acronym>TOAST</> usurps the high-order two bits of the varlena length word,
thereby limiting the logical size of any value of a <acronym>TOAST</>-able
data type to 1Gb (2<superscript>30</> - 1 bytes). When both bits are zero,
the value is an ordinary un-<acronym>TOAST</>ed value of the data type. One
of these bits, if set, indicates that the value has been compressed and must
be decompressed before use. The other bit, if set, indicates that the value
has been stored out-of-line. In this case the remainder of the value is
actually just a pointer, and the correct data has to be found elsewhere. When
both bits are set, the out-of-line data has been compressed too. In each case
the length in the low-order bits of the varlena word indicates the actual size
of the datum, not the size of the logical value that would be extracted by
decompression or fetching of the out-of-line data.
</para>
<para>
If any of the columns of a table are <acronym>TOAST</>-able, the table will
have an associated <acronym>TOAST</> table, whose OID is stored in the table's
<structname>pg_class</>.<structfield>reltoastrelid</> entry. Out-of-line
<acronym>TOAST</>ed values are kept in the <acronym>TOAST</> table, as
described in more detail below.
</para>
<para>
The compression technique used is a fairly simple and very fast member
of the LZ family of compression techniques. See
<filename>src/backend/utils/adt/pg_lzcompress.c</> for the details.
</para>
<para>
Out-of-line values are divided (after compression if used) into chunks of at
most <literal>TOAST_MAX_CHUNK_SIZE</> bytes (this value is a little less than
<literal>BLCKSZ/4</>, or about 2000 bytes by default). Each chunk is stored
as a separate row in the <acronym>TOAST</> table for the owning table. Every
<acronym>TOAST</> table has the columns <structfield>chunk_id</> (an OID
identifying the particular <acronym>TOAST</>ed value),
<structfield>chunk_seq</> (a sequence number for the chunk within its value),
and <structfield>chunk_data</> (the actual data of the chunk). A unique index
on <structfield>chunk_id</> and <structfield>chunk_seq</> provides fast
retrieval of the values. A pointer datum representing an out-of-line
<acronym>TOAST</>ed value therefore needs to store the OID of the
<acronym>TOAST</> table in which to look and the OID of the specific value
(its <structfield>chunk_id</>). For convenience, pointer datums also store the
logical datum size (original uncompressed data length) and actual stored size
(different if compression was applied). Allowing for the varlena header word,
the total size of a <acronym>TOAST</> pointer datum is therefore 20 bytes
regardless of the actual size of the represented value.
</para>
<para>
The <acronym>TOAST</> code is triggered only
when a row value to be stored in a table is wider than <literal>BLCKSZ/4</>
bytes (normally 2Kb). The <acronym>TOAST</> code will compress and/or move
field values out-of-line until the row value is shorter than
<literal>BLCKSZ/4</> bytes or no more gains can be had. During an UPDATE
operation, values of unchanged fields are normally preserved as-is; so an
UPDATE of a row with out-of-line values incurs no <acronym>TOAST</> costs if
none of the out-of-line values change.
</para>
<para>
The <acronym>TOAST</> code recognizes four different strategies for storing
<acronym>TOAST</>-able columns:
<itemizedlist>
<listitem>
<para>
<literal>PLAIN</literal> prevents either compression or
out-of-line storage. This is the only possible strategy for
columns of non-<acronym>TOAST</>-able data types.
</para>
</listitem>
<listitem>
<para>
<literal>EXTENDED</literal> allows both compression and out-of-line
storage. This is the default for most <acronym>TOAST</>-able data types.
Compression will be attempted first, then out-of-line storage if
the row is still too big.
</para>
</listitem>
<listitem>
<para>
<literal>EXTERNAL</literal> allows out-of-line storage but not
compression. Use of <literal>EXTERNAL</literal> will
make substring operations on wide <type>text</type> and
<type>bytea</type> columns faster (at the penalty of increased storage
space) because these operations are optimized to fetch only the
required parts of the out-of-line value when it is not compressed.
</para>
</listitem>
<listitem>
<para>
<literal>MAIN</literal> allows compression but not out-of-line
storage. (Actually, out-of-line storage will still be performed
for such columns, but only as a last resort when there is no other
way to make the row small enough.)
</para>
</listitem>
</itemizedlist>
Each <acronym>TOAST</>-able data type specifies a default strategy for columns
of that data type, but the strategy for a given table column can be altered
with <command>ALTER TABLE SET STORAGE</>.
</para>
<para>
This scheme has a number of advantages compared to a more straightforward
approach such as allowing row values to span pages. Assuming that queries are
usually qualified by comparisons against relatively small key values, most of
the work of the executor will be done using the main row entry. The big values
of <acronym>TOAST</>ed attributes will only be pulled out (if selected at all)
at the time the result set is sent to the client. Thus, the main table is much
smaller and more of its rows fit in the shared buffer cache than would be the
case without any out-of-line storage. Sort sets shrink also, and sorts will
more often be done entirely in memory. A little test showed that a table
containing typical HTML pages and their URLs was stored in about half of the
raw data size including the <acronym>TOAST</> table, and that the main table
contained only about 10% of the entire data (the URLs and some small HTML
pages). There was no runtime difference compared to an un-<acronym>TOAST</>ed
comparison table, in which all the HTML pages were cut down to 7Kb to fit.
</para>
</sect1>
<sect1 id="storage-page-layout">
<title>Database Page Layout</title>
<para>
This section provides an overview of the page format used within
<productname>PostgreSQL</productname> tables and indexes.<footnote>
<para>
Actually, index access methods need not use this page format.
......@@ -22,7 +341,7 @@ This section provides an overview of the page format used by
the item layout rules.
</para>
</footnote>
TOAST tables and sequences are formatted just like a regular table.
Sequences and <acronym>TOAST</> tables are formatted just like a regular table.
</para>
<para>
......@@ -36,7 +355,7 @@ an item is a row; in an index, an item is an index entry.
<para>
Every table and index is stored as an array of <firstterm>pages</> of a
fixed size (usually 8K, although a different page size can be selected
fixed size (usually 8Kb, although a different page size can be selected
when compiling the server). In a table, all the pages are logically
equivalent, so a particular item (row) can be stored in any page. In
indexes, the first page is generally reserved as a <firstterm>metapage</>
......@@ -348,7 +667,10 @@ data. Empty in ordinary tables.</entry>
All variable-length datatypes share the common header structure
<type>varattrib</type>, which includes the total length of the stored
value and some flag bits. Depending on the flags, the data may be either
inline or in another table (TOAST); it might be compressed, too.
inline or in a <acronym>TOAST</> table;
it might be compressed, too (see <xref linkend="storage-toast">).
</para>
</sect1>
</chapter>
<!--
$PostgreSQL: pgsql/doc/src/sgml/xtypes.sgml,v 1.24 2005/01/08 22:13:38 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/xtypes.sgml,v 1.25 2005/01/10 00:04:38 tgl Exp $
-->
<sect1 id="xtypes">
......@@ -232,10 +232,14 @@ CREATE TYPE complex (
</para>
<para>
<indexterm>
<primary>TOAST</primary>
<secondary>and user-defined types</secondary>
</indexterm>
If the values of your data type might exceed a few hundred bytes in
size (in internal form), you should make the data type
TOAST-able.<indexterm><primary>TOAST</primary><secondary>and
user-defined types</secondary></indexterm> To do this, the internal
<acronym>TOAST</>-able (see <xref linkend="storage-toast">).
To do this, the internal
representation must follow the standard layout for variable-length
data: the first four bytes must be an <type>int32</type> containing
the total length in bytes of the datum (including itself). The C
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册