You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
$ uname -a
Linux azure 6.5.0-1018-azure #19~22.04.2-Ubuntu SMP Thu Mar 21 16:45:46 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
$ mysqld --version
/usr/sbin/mysqld Ver 8.0.36-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))
$ /usr/lib/postgresql/16/bin/postgres --version
postgres (PostgreSQL) 16.2 (Ubuntu 16.2-1.pgdg22.04+1)
$ apt show -a pgloader
Package: pgloader
Version: 3.6.10-2.pgdg22.04+1
Priority: optional
Section: database
Maintainer: Dimitri Fontaine <dim@tapoueh.org>
Installed-Size: 33.9 MB
Depends: freetds-dev, libc6 (>= 2.34), zlib1g (>= 1:1.1.4), libsqlite3-0, libssl3
Homepage: https://github.com/dimitri/pgloader
Download-Size: 29.3 MB
APT-Sources: https://apt.postgresql.org/pub/repos/apt jammy-pgdg/main amd64 Packages
Description: extract, transform and load data into PostgreSQL
pgloader imports data from different kind of sources and COPY it into
PostgreSQL.
.
The command language is described in the manual page and allows one to
describe where to find the data source, its format, and to describe data
processing and transformation.
.
Supported source formats include CSV, fixed width flat files, dBase3 files
(DBF), and SQLite and MySQL databases. In most of those formats, pgloader
is able to auto-discover the schema and create the tables and the indexes
in PostgreSQL. In the MySQL case it's possible to edit CASTing rules from the pgloader command directly.Package: pgloaderVersion: 3.6.3-1ubuntu1Priority: extraSection: universe/miscOrigin: UbuntuMaintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>Original-Maintainer: Dimitri Fontaine <dim@tapoueh.org>Bugs: https://bugs.launchpad.net/ubuntu/+filebugInstalled-Size: 33.8 MBDepends: freetds-dev, libc6 (>= 2.34), zlib1g (>= 1:1.1.4), libssl3Homepage: https://github.com/dimitri/pgloaderDownload-Size: 29.5 MBAPT-Sources: http://azure.archive.ubuntu.com/ubuntu jammy/universe amd64 PackagesDescription: extract, transform and load data into PostgreSQL pgloader imports data from different kind of sources and COPY it into PostgreSQL. . The command language is described in the manual page and allows one to describe where to find the data source, its format, and to describe data processing and transformation. . Supported source formats include CSV, fixed width flat files, dBase3 files (DBF), and SQLite and MySQL databases. In most of those formats, pgloader is able to auto-discover the schema and create the tables and the indexes in PostgreSQL. In the MySQL case it's possible to edit CASTing rules from
the pgloader command directly.
LOAD DATABASE
FROM mysql://username:password@localhost/tbm
INTO pgsql://username:password@localhost/tbm
WITH quote identifiers, schema only
INCLUDING ONLY TABLE NAMES MATCHING ~/^tbm(_|i_(?!imageInReply)|c_f(78579|2265748|17019292|25459979|27546680))/
;
By default, as many CREATE INDEX threads as the maximum number of indexes per table are found in your source schema. It is possible to set the max parallel create index WITH option to another number in case there’s just too many of them to create.
由于未知原因在3.6.af8c3c1中WITH max parallel create index 1并没有实际作用仍然会执行上述流程所以不得不分开在WITH schema only的.load中先完成CREATE INDEXdimitri/pgloader#1576 以减少io争用
flowchart TD
fields[tid\nthreadType\nstickyType\ntopicType\nisGood\ntitle\nauthorUid\npostedAt\nlatestReplyPostedAt\nlatestReplierUid\nreplyCount\nviewCount\nshareCount\nagreeCount\ndisagreeCount\nzan\ngeolocation\nauthorPhoneType\ncreatedAt\nupdatedAt\nlastSeenAt]
fields --CONCAT_WS(' ', ...)--> row[row\nsingle long csv line string but using space and without any escape]
row --sha2-256--> row_hash
row_hash --GROUP_CONCAT(... ORDER BY tid SEPARATOR '\n')--> rows_hash[rows_hash\nnew line delimited rows]
rows_hash --sha2-256--> table_hash
Loading
mysql
SET SESSION group_concat_max_len =18446744073709551615; -- 默认只有1024超过会带WARNING截断 https://dba.stackexchange.com/questions/197746/why-set-group-concat-max-len-below-the-maximumSELECT SHA2(GROUP_CONCAT(SHA2(CONCAT_WS('',
tid, threadType, stickyType, topicType, isGood, title, authorUid, postedAt, latestReplyPostedAt, latestReplierUid, replyCount, viewCount, shareCount, agreeCount, disagreeCount, LOWER(HEX(zan)), LOWER(HEX(geolocation)), authorPhoneType, createdAt, updatedAt, lastSeenAt
), 512) ORDER BY tid SEPARATOR '\n'), 512) hash FROM tbmc_f97650_thread;
ALTERUSER tbm WITH SUPERUSER;
CREATE EXTENSION IF NOT EXISTS mysql_fdw;
ALTERUSER tbm WITH NOSUPERUSER;
CREATE SERVER IF NOT EXISTS mysql
FOREIGN DATA WRAPPER mysql_fdw
OPTIONS (host '127.0.0.1', port '3306');
CREATEUSERMAPPING IF NOT EXISTS FOR CURRENT_USER-- 当前登录用户 也可以用PUBLIC使得用户名/密码对所有pgsql用户可见
SERVER mysql
OPTIONS (username 'username', password 'password');
清理:
DROPUSER MAPPING IF EXISTS FOR CURRENT_USER SERVER mysql; -- FOR CURRENT_USER同上DROPSCHEMA IF EXISTS mysql CASCADE;
DROP SERVER IF EXISTS mysql CASCADE;
DROP EXTENSION IF EXISTS mysql_fdw CASCADE;
DROPDATABASE contrib_regression; -- make USE_PGXS=1 installcheck所产生
4.3. 在pgsql中创建fdw表
CREATESCHEMAIF NOT EXISTS mysql;
CREATE FOREIGN TABLE IF NOT EXISTS mysql."tbmc_f97650_thread"()
INHERITS (tbm."tbmc_f97650_thread")
SERVER mysql
OPTIONS (dbname 'tbm', table_name 'tbmc_f97650_thread');
4.3.1. 被继承表tbmc_f97650_thread无法再直接UPDATE/DELETE返回[HV005] ERROR: system attribute "tableoid" can't be fetched from remote relation而必须加ONLYEnterpriseDB/mysql_fdw#300 但仍然可以INSERT
-- symmetric difference FULL OUTER JOIN USING (...columns)SELECTCOUNT(*) FROM mysql."tbmi_ocr_box_Latn" a FULL OUTER JOIN ONLY tbm."tbmi_ocr_box_Latn" b
USING ("imageId", "frameIndex", "centerPointX", "centerPointY", "width", "height", "rotationDegrees", "recognizer", "confidence", "text")
WHERE (a."imageId" IS NULLAND a."frameIndex" IS NULLAND a."centerPointX" IS NULLAND a."centerPointY" IS NULLAND a."width" IS NULLAND a."height" IS NULLAND a."rotationDegrees" IS NULLAND a."recognizer" IS NULLAND a."confidence" IS NULLAND a."text" IS NULL)
OR (b."imageId" IS NULLAND b."frameIndex" IS NULLAND b."centerPointX" IS NULLAND b."centerPointY" IS NULLAND b."width" IS NULLAND b."height" IS NULLAND b."rotationDegrees" IS NULLAND b."recognizer" IS NULLAND b."confidence" IS NULLAND b."text" IS NULL);
SELECT'SELECT COUNT(*) FROM mysql."'||
table_name ||'" a FULL OUTER JOIN ONLY tbm."'||
table_name ||
E'" b \n USING ("'||
string_agg(column_name, '", "'ORDER BY ordinal_position) ||
E'")\nWHERE (a."'||
string_agg(column_name, '" IS NULL AND a."'ORDER BY ordinal_position) ||
E'" IS NULL)\n OR (b."'||
string_agg(column_name, '" IS NULL AND b."'ORDER BY ordinal_position) ||'" IS NULL);'FROMinformation_schema.columnsWHERE table_schema ='tbm'AND table_name ='tbmi_ocr_box_Latn'GROUP BY table_name;
Finally, NATURAL is a shorthand form of USING: it forms a USING list consisting of all column names that appear in both input tables. As with USING, these columns appear only once in the output table. If there are no common column names, NATURAL JOIN behaves like JOIN ... ON TRUE, producing a cross-product join.
5.1.2.1. 因而需要将5.1.1.中的USING (...columns)展开成对所有列=operator以及额外的OR (IS NULL AND IS NULL)
SELECTCOUNT(*) FROM mysql."tbm_bilibiliVote" a FULL OUTER JOIN ONLY tbm."tbm_bilibiliVote" b ON
(a."pid"= b."pid") AND
(a."authorUid"= b."authorUid") AND
(a."authorExpGrade"= b."authorExpGrade"OR (a."authorExpGrade" IS NULLAND b."authorExpGrade" IS NULL)) AND
(a."isValid"= b."isValid") AND
(a."voteBy"= b."voteBy"OR (a."voteBy" IS NULLAND b."voteBy" IS NULL)) AND
(a."voteFor"= b."voteFor"OR (a."voteFor" IS NULLAND b."voteFor" IS NULL)) AND
(a."replyContent"= b."replyContent") AND
(a."postTime"= b."postTime")
WHERE (a."pid" IS NULLAND a."authorUid" IS NULLAND a."authorExpGrade" IS NULLAND a."isValid" IS NULLAND a."voteBy" IS NULLAND a."voteFor" IS NULLAND a."replyContent" IS NULLAND a."postTime" IS NULL)
OR (b."pid" IS NULLAND b."authorUid" IS NULLAND b."authorExpGrade" IS NULLAND b."isValid" IS NULLAND b."voteBy" IS NULLAND b."voteFor" IS NULLAND b."replyContent" IS NULLAND b."postTime" IS NULL);
5.1.2.1.1. 不对NOT NULL的列做额外判断是为了避免7.1.中的
FULL JOIN is only supported with merge-joinable or hash-joinable join conditions
5.1.2.2. 一键生成
SELECT'SELECT COUNT(*) FROM mysql."'||
table_name ||'" a FULL OUTER JOIN ONLY tbm."'||
table_name ||
E'" b ON\n'||
string_agg(
' (a."'||
column_name ||'" = b."'||
column_name ||
CASE WHEN is_nullable::bool = true THEN
'" OR (a."'||
column_name ||'" IS NULL AND b."'||
column_name ||'" IS NULL))'
ELSE '")'
END,
E' AND\n'ORDER BY ordinal_position
) ||
E'\nWHERE (a."'||
string_agg(column_name, '" IS NULL AND a."'ORDER BY ordinal_position) ||
E'" IS NULL)\n OR (b."'||
string_agg(column_name, '" IS NULL AND b."'ORDER BY ordinal_position) ||'" IS NULL);'FROMinformation_schema.columnsWHERE table_schema ='tbm'AND table_name ='tbm_bilibiliVote'GROUP BY table_name;
-- symmetric difference FULL OUTER JOIN row(table)SELECTCOUNT(*) FROM mysql."tbmi_ocr_box_Latn" a FULL OUTER JOIN ONLY tbm."tbmi_ocr_box_Latn" b ON row(a) = row(b) WHERE a IS NULLOR b IS NULL;
-- non symmetric difference EXCEPT mysqlSELECTCOUNT(*) FROM (SELECT*FROM ONLY tbm."tbmi_ocr_box_Latn" a EXCEPT SELECT*FROM mysql."tbmi_ocr_box_Latn" b) t;
-- non symmetric difference EXCEPT tbmSELECTCOUNT(*) FROM (SELECT*FROM mysql."tbmi_ocr_box_Latn" a EXCEPT SELECT*FROM ONLY tbm."tbmi_ocr_box_Latn" b) t;
-- symmetric difference EXCEPTSELECTCOUNT(*) FROM (
SELECT*FROM ONLY tbm."tbmi_ocr_box_Latn" a EXCEPT SELECT*FROM mysql."tbmi_ocr_box_Latn" b
UNION ALLSELECT*FROM mysql."tbmi_ocr_box_Latn" a EXCEPT SELECT*FROM ONLY tbm."tbmi_ocr_box_Latn" b
) t;
-- non symmetric difference WHERE NOT EXISTS mysqlSELECTCOUNT(*) FROM (SELECT*FROM ONLY tbm."tbmi_ocr_box_Latn" a WHERE NOT EXISTS (SELECT*FROM mysql."tbmi_ocr_box_Latn" b)) t;
因为SELECT * FROM mysql."tbmi_ocr_box_Latn"在此显然返回非0行(因而只需要读一行mysql."tbmi_ocr_box_Latn"几百ms后就会立即返回)从而使得该predicate在非空表上恒真
-- non symmetric difference WHERE NOT EXISTS tbmSELECTCOUNT(*) FROM (SELECT*FROM mysql."tbmi_ocr_box_Latn" a WHERE NOT EXISTS (SELECT*FROM ONLY tbm."tbmi_ocr_box_Latn" b)) t;
-- symmetric difference WHERE NOT EXISTSSELECTCOUNT(*) FROM (
SELECT*FROM ONLY tbm."tbmi_ocr_box_Latn" a WHERE NOT EXISTS (SELECT*FROM mysql."tbmi_ocr_box_Latn" b)
UNION ALLSELECT*FROM mysql."tbmi_ocr_box_Latn" a WHERE NOT EXISTS (SELECT*FROM ONLY tbm."tbmi_ocr_box_Latn" b)
) t;
createtablespacetemp_tablespace owner postgres location '{目的路径}';
grant create on tablespace temp_tablespace to public;
alter system set temp_tablespaces = temp_tablespace;
select pg_reload_conf();
create temp table a(b text);
5.2. 总行数对比
SELECT a, b, b - a diff FROM (SELECT (SELECTCOUNT(*) FROM mysql."tbmi_ocr_box_Latn") a, (SELECTCOUNT(*) FROM ONLY tbm."tbmi_ocr_box_Latn") b) t;
a | b | diff
--------+--------+--------472655 | 472655 | 0
(1 row)
SELECT CONCAT(
'CREATE FOREIGN TABLE IF NOT EXISTS mysql."',
TABLE_NAME,
'"() INHERITS (tbm."',
TABLE_NAME,
'") SERVER mysql OPTIONS (dbname \'tbm\', table_name \'',
TABLE_NAME,
'\');\n',
'SELECT a, b, b - a diff FROM (SELECT (SELECT COUNT(*) FROM mysql."',
TABLE_NAME,
'") a, (SELECT COUNT(*) FROM ONLY tbm."',
TABLE_NAME,
'") b) t;\n',
'SELECT COUNT(*) FROM mysql."',
TABLE_NAME,
'" a FULL OUTER JOIN ONLY tbm."',
TABLE_NAME,
'" b ON row(a) = row(b) WHERE a IS NULL OR b IS NULL;'
)
FROMinformation_schema.TABLESWHERE TABLE_SCHEMA ='tbm'AND TABLE_NAME REGEXP '^tbm(_|i_(?!imageInReply)|c_f(78579|2265748|17019292|25459979|27546680))'ORDER BY TABLE_NAME;
REGEXP部分同2.1.
6.1. 一键bash
cd pgloader &&# 1.1.1.中git clone的pgloader目录路径
(
/usr/bin/time -v build/bin/pgloader schema.load &&
/usr/bin/time -v build/bin/pgloader data.load &&
PGPASSWORD=password /usr/bin/time -v psql -aUusername tbm < compare.sql
) 2>&1| tee -a stdout+err
SELECTCOUNT(*) FROM (SELECT*FROM ONLY tbm."tbm_bilibiliVote" a EXCEPT SELECT*FROM mysql."tbm_bilibiliVote" b) t;
SELECTCOUNT(*) FROM (SELECT*FROM mysql."tbm_bilibiliVote" a EXCEPT SELECT*FROM ONLY tbm."tbm_bilibiliVote" b) t;
SELECT*FROM mysql."tbm_bilibiliVote" a FULL OUTER JOIN ONLY tbm."tbm_bilibiliVote" b ON
a."authorUid"= b."authorUid"AND
a."isValid"= b."isValid"AND
a."pid"= b."pid"AND
a."postTime"= b."postTime"AND
a."replyContent"= b."replyContent"AND
a."voteBy"= b."voteBy"AND
a."voteFor"= b."voteFor"AND
a."authorExpGrade"= b."authorExpGrade"WHERE (a."pid" IS NULLAND a."authorUid" IS NULLAND a."authorExpGrade" IS NULLAND a."isValid" IS NULLAND a."voteBy" IS NULLAND a."voteFor" IS NULLAND a."replyContent" IS NULLAND a."postTime" IS NULL)
OR (b."pid" IS NULLAND b."authorUid" IS NULLAND b."authorExpGrade" IS NULLAND b."isValid" IS NULLAND b."voteBy" IS NULLAND b."voteFor" IS NULLAND b."replyContent" IS NULLAND b."postTime" IS NULL);
The SQL data types 'date', 'time', and 'timestamp' are field based time values which are intended to be zone offset independent: they are actually, technically, floating time values! The data type 'timestamp with time zone' is the zone offset-dependent equivalent of 'timestamp' in SQL.
Floating Time
Some observed time values are not related to a specific moment in incremental time. Instead, they need to be combined with local information to determine a range of acceptable incremental time values. We refer to these sorts of time values as "floating times". Floating times are not attached and should never be attached to a particular time zone.
Examples of floating time events include a user’s birth date, an employee’s hire or termination date, or a list of company holidays.
For example, suppose that January 1st is considered a holiday in your application. The day "January 1" has a floating-time status as a "holiday". That "day" can begin as early as midnight GMT-14:00 and end as late as midnight of January 2 GMT+12:00, depending on local time. This covers an incremental time range of fifty hours.
SET SESSION TIME ZONE 'Asia/Shanghai';
SELECTa.pid, a."postTime", b."postTime"FROM mysql."tbm_bilibiliVote" a JOIN ONLY tbm."tbm_bilibiliVote" b ONa.pid=b.pidORDER BY pid;
pid
postTime
postTime
124498114574
2019-03-10 12:38:17.000000 +08:00
2019-03-10 12:38:17.000000 +08:00
124498200265
2019-03-10 12:44:12.000000 +08:00
2019-03-10 12:44:12.000000 +08:00
因而只需要修改pgsql时区
SET SESSION TIME ZONE 'Asia/Shanghai';
SELECTCOUNT(*) FROM ONLY mysql."tbm_bilibiliVote" a FULL OUTER JOIN ONLY tbm."tbm_bilibiliVote" b ON row(a) = row(b) WHERE a IS NULLOR b IS NULL;
便校验一致
7.2.2. 如果执行了7.1.5.需再在pgsql端将该列类型改回json或jsonb
DROP FOREIGN TABLE mysql."tbm_bilibiliVote"; -- 避免`ALTER TABLE`由于`4.3.2.`而返回假阳性之`ERROR: "tbm_bilibiliVote" is not a table`ALTERTABLE tbm."tbm_bilibiliVote" ALTER "replyContent" TYPE jsonb USING "replyContent"::jsonb;
-- 修改`6.`中的`TABLE_NAME REGEXP '...'`predicate为`TABLE_NAME = 'tbm_bilibiliVote'`并eval其输出以重新执行校验
VACUUM FULL tbm."tbm_bilibiliVote";
设有表tbmi_metadata_gif结构:
CREATETABLE `tbmi_metadata_gif` (
`imageId`int unsigned NOT NULL,
`repeatCount`smallint unsigned NOT NULL,
`colorTableMode` enum('Global','Local') NOT NULL,
`globalColorTableLength`intNOT NULL,
`comments` json DEFAULT NULL,
PRIMARY KEY (`imageId`)
) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
SELECTCOUNT(*) FROM mysql."tbmi_metadata_gif" a FULL OUTER JOIN ONLY tbm."tbmi_metadata_gif" b ON
(a."imageId"= b."imageId") AND
(a."repeatCount"= b."repeatCount") AND
(a."colorTableMode"= b."colorTableMode") AND
(a."globalColorTableLength"= b."globalColorTableLength") AND
(a."comments"::jsonb = b."comments"::jsonb OR (a."comments" IS NULLAND b."comments" IS NULL))
WHERE (a."imageId" IS NULLAND a."repeatCount" IS NULLAND a."colorTableMode" IS NULLAND a."globalColorTableLength" IS NULLAND a."comments" IS NULL)
OR (b."imageId" IS NULLAND b."repeatCount" IS NULLAND b."colorTableMode" IS NULLAND b."globalColorTableLength" IS NULLAND b."comments" IS NULL);
- (a."comments"::jsonb = b."comments"::jsonb OR (a."comments" IS NULL AND b."comments" IS NULL))+ (a."comments"::text = b."comments"::text OR (a."comments" IS NULL AND b."comments" IS NULL))
pgloader@3.6.10mysql_fdw@2.9.1库名假定为
tbm1.1 由于 dimitri/pgloader#1211 ,从postgre社区apt source安装的
pgloader 3.6.10-2.pgdg22.04+1无法连接mysqld1.1.1. 建议从源码自行编译
3.6.101.1.2. 清理:
sudo apt purge -y pgloader && sudo apt autoremove --purge1.2. 由于 dimitri/pgloader#782 ,pgloader只能通过仍在使用
mysql 8.0.34中deprecated的mysql_native_password用户登录mysqld1.2.1. 建议ad-hoc地临时创建一个最小权限(只
GRANT库/表级权限)用户用于pgloader而非修改现有用户的default auth pluginschema.load有LOAD DATABASE FROM mysql://username:password@localhost/tbm INTO pgsql://username:password@localhost/tbm WITH quote identifiers, schema only INCLUDING ONLY TABLE NAMES MATCHING ~/^tbm(_|i_(?!imageInReply)|c_f(78579|2265748|17019292|25459979|27546680))/ ;2.1. 可以使用
INCLUDING ONLY TABLE NAMES MATCHING ~/regexp/, 'exact'来filter要导入的表名2.2. 令文件
data.load有LOAD DATABASE FROM mysql://username:password@localhost/tbm INTO pgsql://username:password@localhost/tbm SET MySQL PARAMETERS -- https://github.com/dimitri/pgloader/issues/999 net_read_timeout = '300', net_write_timeout = '300' WITH quote identifiers, data only, on error stop, workers = 2, concurrency = 1, batch rows = 1000, batch size = 8MB, prefetch rows = 10000 -- 酌情tunning https://pgloader.readthedocs.io/en/latest/command.html#batch-behaviour-options INCLUDING ONLY TABLE NAMES MATCHING ~/^tbm(_|i_(?!imageInReply)|c_f(78579|2265748|17019292|25459979|27546680))/ ;2.3. 分开导入
表结构和表数据是因为pgloader默认行为是按照指定和mysql://源下默认的CASTING创建目的表结构(除了索引),再COPY导入表数据,最后并行{表索引数量}个CREATE INDEX并且不等待其完成而是并行地开始下一个表的过程: https://pgloader.readthedocs.io/en/latest/batches.html由于未知原因在
3.6.af8c3c1中WITH max parallel create index 1并没有实际作用仍然会执行上述流程所以不得不分开在WITH schema only的.load中先完成CREATE INDEXdimitri/pgloader#1576 以减少io争用2.3.1. 出于类似的原因也建议先禁用
autovacuum清理:待pgloader完成后再手动
VACUMM FULLhttps://dba.stackexchange.com/questions/130496/is-it-worth-it-to-run-vacuum-on-a-table-that-only-receives-inserts/130514#130514ALTER SYSTEM RESET autovacuum; SELECT pg_reload_conf(); VACUMM FULL;2.4.
WITH quote identifiers是为了方便后续校验两端表数据是否一致时可以假定列名相同,您可以之后再重命名为postgre社区习惯的snake_case以减少满屏幕""(相当于mysql的``)2.5. 视奸正在导入什么表 https://stackoverflow.com/questions/35319597/how-to-stop-kill-a-query-in-postgresql
可根据
表大小猜测进度 https://stackoverflow.com/questions/21738408/postgresql-list-and-order-tables-by-size/21738505#21738505tbmc_f97650_thread结构mysql端:
由pgloader在pgsql端按照
2.3.中mysql://源下默认的CASTING所创建:3.1. 可以通过将表中
每列值concat起来hash后再aggreate地对每行hash再hash来得出整个表的hashmermaid.live
flowchart TD fields[tid\nthreadType\nstickyType\ntopicType\nisGood\ntitle\nauthorUid\npostedAt\nlatestReplyPostedAt\nlatestReplierUid\nreplyCount\nviewCount\nshareCount\nagreeCount\ndisagreeCount\nzan\ngeolocation\nauthorPhoneType\ncreatedAt\nupdatedAt\nlastSeenAt] fields --CONCAT_WS(' ', ...)--> row[row\nsingle long csv line string but using space and without any escape] row --sha2-256--> row_hash row_hash --GROUP_CONCAT(... ORDER BY tid SEPARATOR '\n')--> rows_hash[rows_hash\nnew line delimited rows] rows_hash --sha2-256--> table_hashmysql
blob类型字段必须套
LOWER(HEX())因为mysql默认的blob2text输出类似0xDEADBEAF形式而pgsql是deadbeafpgsql
3.2. 然而在大表上两者最终都会耗尽内存而失败,并且需要
LOWER(HEX())GROUP_CONCAT()时按照PK或UK排序行否则在pgsql中默认迫真假随机序一键生成3.3. 理论上可以通过赋予纯SQL(无需臭名昭著的命令式
存储过程或外部程序辅助)图灵完备性 https://wiki.postgresql.org/wiki/Mandelbrot_set 导致其踏入了同一条Turing Tarpit之河流的rCTE来LIMIT 10000地每次hash的行并对每10k行aggreate之hash再aggreate地hash从而减少每行hash所aggreate的字符串长度mermaid.live
flowchart TD fields[tid\nthreadType\nstickyType\ntopicType\nisGood\ntitle\nauthorUid\npostedAt\nlatestReplyPostedAt\nlatestReplierUid\nreplyCount\nviewCount\nshareCount\nagreeCount\ndisagreeCount\nzan\ngeolocation\nauthorPhoneType\ncreatedAt\nupdatedAt\nlastSeenAt] fields --LIMIT 10000\nCONCAT_WS(' ', ...)--> row fields --LIMIT 10000\nCONCAT_WS(' ', ...)--> row fields --LIMIT 10000\nCONCAT_WS(' ', ...)--> row fields --LIMIT 10000\nCONCAT_WS(' ', ...)--> row fields --LIMIT rest\nCONCAT_WS(' ', ...)--> row row[row\nsingle long csv line string but using space and without any escape] row --LIMIT 10000\nsha2-256--> row_hash row --LIMIT 10000\nsha2-256--> row_hash row --LIMIT 10000\nsha2-256--> row_hash row --LIMIT 10000\nsha2-256--> row_hash row --LIMIT rest\nsha2-256--> row_hash row_hash --GROUP_CONCAT(... ORDER BY tid SEPARATOR '\n')--> rows_hash[rows_hash\nnew line delimited rows] rows_hash --sha2-256--> table_hash某so纯路人指出可以使用postgre社区牛逼哄哄的某知名企业EnterpriseDB所开发的某pgsql扩展 https://github.com/EnterpriseDB/mysql_fdw 通过foreign data wrapper来在pgsql中将外部数据当做迫真物化视图查询4.1. 安装:
清理:
4.2. 安装该pgsql扩展
清理:
4.3. 在pgsql中创建fdw表
使用
table inheritance是为了避免在2.已经pgloader schema.load后再写一遍fdw表结构,但这会导致4.3.1. 被继承表
tbmc_f97650_thread无法再直接UPDATE/DELETE返回[HV005] ERROR: system attribute "tableoid" can't be fetched from remote relation而必须加ONLYEnterpriseDB/mysql_fdw#300 但仍然可以INSERT4.3.1.1. 这个bug可能会在未来版本的
mysql_fdw中修复但在这里并不重要,因为这种迫真假锁全表正好避免了在校验期间UPDATE/DELETE(但防不了INSERT)(默认事务隔离级别是READ COMMITTED而非mysql默认的REPEATABLE READ#32 (comment) 当然您也可以改)了pgsql表导致假阴性4.3.2. 由于
table inheritance的本质:也会出现
中的所有结果:也就是被继承表并集了所有继承表(在这里通过查询mysql),相当于
tbmi_ocr_box_Latn结构:mysql端:
由pgloader在pgsql端按照
2.3.中mysql://源下默认的CASTING所创建:5.1. 根据
某so纯路人symmdifftable有4种写法:5.1.1. https://stackoverflow.com/questions/15330403/find-difference-between-two-big-tables-in-postgresql/15333054#15333054
5.1.1.1.
一键生成5.1.1.2. 也可以使用
NATURAL FULL OUTER JOIN ONLYhttps://www.postgresql.org/docs/current/queries-table-expressions.html 省略USING (...)5.1.2. 但
5.1.1.和5.1.1.2.并不能用于有列类型允许NULL之存在的表如7.中的tbm_bilibiliVote表有着因为根据sql特色之 https://en.wikipedia.org/wiki/Three-valued_logic
NULL = NULL是UNKNOWN而UNKNOWN转bool是false于是不会被ON/WHERE等clause视作相同 https://stackoverflow.com/questions/14366004/sql-server-join-missing-null-values5.1.2.1. 因而需要将
5.1.1.中的USING (...columns)展开成对所有列=operator以及额外的OR (IS NULL AND IS NULL)5.1.2.1.1. 不对
NOT NULL的列做额外判断是为了避免7.1.中的5.1.2.2.
一键生成5.1.3. https://stackoverflow.com/questions/15330403/find-difference-between-two-big-tables-in-postgresql/49381589#49381589
5.1.3.1. 其中
row(t)是 https://www.postgresql.org/docs/current/sql-expressions.html#SQL-SYNTAX-ROW-CONSTRUCTORS5.1.3.2. 这也避免了
5.1.2.5.1.4. https://stackoverflow.com/questions/6337871/how-can-i-speed-up-a-diff-between-tables
由于
EXCEPT不是symmdiff之FULL OUTER JOIN而是SELECT a.* FROM a LEFT OUTER JOIN b所以只能执行两遍2x耗时正如同没有FULL OUTER JOIN可用的mysql人 https://stackoverflow.com/questions/4796872/how-can-i-do-a-full-outer-join-in-mysql5.1.4.1. 由于未知bug
UNION ALL后始终为0行即便单独查询有行5.1.4.2. 这也避免了
5.1.2.5.1.5. 典型的误用
NOT EXISTS因为
SELECT * FROM mysql."tbmi_ocr_box_Latn"在此显然返回非0行(因而只需要读一行mysql."tbmi_ocr_box_Latn"几百ms后就会立即返回)从而使得该predicate在非空表上恒真5.1.5.1. 由于未知bug
UNION ALL后始终为0行即便单独查询有非0行5.1.6. 除




5.1.5.外的3种执行期间均需pgsql消耗1x表大小的内存(总比3.2.好)和3x表大小的临时表存储5.1.6.1. 可以修改
临时表空间在fs上的默认路径/var/lib/postgresql/16/main/base/pgsql_tmphttps://dba.stackexchange.com/questions/170661/in-postgres-how-do-i-adjust-the-pgsql-tmp-setting/170665#170665tmp={目的路径} && mkdir -p $tmp /PG_16_202307071 && chown -R postgres: $tmp && chmod -R 700 $tmp5.2. 总行数对比
compare.sql有在mysql中codegen出基于5.1.3.、4.3.和5.2.校验用的一键生成pgsqlREGEXP部分同
2.1.6.1. 一键bash
tbm_bilibiliVote结构 https://github.com/n0099/bilibiliVotemysql端:
由pgloader在pgsql端按照
2.3.中mysql://源下默认的CASTING所创建:7.1. 使用
5.1.3.中的symmetric difference FULL OUTER JOIN row(table)可得 https://stackoverflow.com/questions/47405732/why-does-postgresql-throw-full-join-is-only-supported-with-merge-joinable-or-ha
7.1.1. 使用
5.1.5.中的non symmetric difference EXCEPT可得 https://stackoverflow.com/questions/48420438/could-not-identify-an-equality-operator-for-type-json-when-using-distinct
7.1.2. 执行
5.1.2.1.会有 https://stackoverflow.com/questions/32843213/operator-does-not-exist-json-json
7.1.3.
7.中可见列replyContent是json类型这恰好等于
5.2.之和
7.1.4. 但即便是
json::text也仍然相同
7.1.5. 可以在mysql端将该
json列转text后再重新按照
2.2.仅导入该表(INCLUDING ONLY TABLE NAMES MATCHING 'tbm_bilibiliVote')7.2. 但即便两端的列
replyContent重新导入后均为text类型也仍然有所有行不同实际上是由于列
postTimemysql无时区类型datetime由2.3.中的pgloadermysql://默认的CASTING转为pgsql
有时区类型timestamptz时由pgloader根据系统时区UTC+8而非pgsql时区UTC+0转换为UTC7.2.1. 由于
timestamptz实际上并没有额外存储时区UTC offsethttps://stackoverflow.com/questions/5876218/difference-between-timestamps-with-without-time-zone-in-postgresql因而其并不能解决 https://z.n0099.net/#narrow/near/86397 https://old.reddit.com/r/PostgreSQL/comments/xpygbh/when_would_i_ever_use_timestamp_over_timestamptz/ https://news.ycombinator.com/item?id=20212671 中争论的
带时区引用未来指定datetime问题https://www.w3.org/International/wiki/WorkingWithTimeZones#Past_and_Future_Events
https://www.w3.org/International/wiki/WorkingWithTimeZones#Floating_Time
再叠加mysql_fdw不论当前
pgsql时区https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-TIMEZONES 是什么都不会在将从mysql读到的datetime转换为timestamptz时根据时区重新计算时间而是只修改offset(也就是使用跟mysql类型datetime语义相同的pgsql类型timestamp的语义)因而只需要修改
pgsql时区便校验一致
7.2.2. 如果执行了
7.1.5.需再在pgsql端将该列类型改回json或jsonbtbmi_metadata_gif结构:由pgloader在pgsql端按照
2.3.中mysql://源下默认的CASTING所创建:8.1. 按照
7.1.3.有
8.1.1. 但按照
7.1.4.就无差异8.1.2. 这是由于
jsonb类型不同于本质text的json,会将json解析后存储为pgsql内置类型之集,再由于text类型受c人最爱的https://en.wikipedia.org/wiki/Null-terminated_string 影响不支持存储256个字节中的唯一一个0x00导致jsonb也无法存储\u0000转义后的0x00进其内部的textturbot/steampipe-postgres-fdw#118https://www.postgresql.org/docs/current/datatype-json.html
8.2. 该表该列中共有如下
\u0000https://codepoints.net/U+0000[" ", "Created by TechSmith\u0000"]["tvc\u0000"]["Author:\t\tGod\_job\_dave\u0000\u0000\u0000???\u0001\u0000\u0000\u0000\u0000P??\u0001\u0000"]["Optimized by Ulead SmartSaver!\u0000"]["CREATOR: gd-jpeg v1.0 \(using IJG JPEG v62\), quality = 75\n\u0000"]8.2.1. 使用如下3层如同
6.中codegen迫真元编程自我复制的quine病毒可在mysql端检查所有含有0x00的text类型及其各个长度变种 https://stackoverflow.com/questions/13932750/tinytext-text-mediumtext-and-longtext-maximum-storage-sizes 列并生成ALTER列类型为BLOB(pgloader在pgsql端按照2.3.中mysql://源下默认的CASTING会转换为可存储所有256个字节的bytea)和SELECT查阅的一键生成mysql:REGEXP部分同
2.1.ALTER TABLE tbm.`tbmi_metadata_embedded_exif` MODIFY `userComment` BLOB NOT NULL;SELECT * FROM tbm.`tbmi_metadata_embedded_exif` WHERE CAST(`userComment` AS BINARY) LIKE CONCAT('%', 0x00, '%');ALTER TABLE tbm.`tbmi_metadata_embedded_exif` MODIFY `xpAuthor` BLOB NOT NULL;SELECT * FROM tbm.`tbmi_metadata_embedded_exif` WHERE CAST(`xpAuthor` AS BINARY) LIKE CONCAT('%', 0x00, '%');8.3. 值得注意的是
2.2.导入 dimitri/pgloader#1573 不会有截断0x00后字节的WARNING且6.校验 EnterpriseDB/mysql_fdw#299 过程中也无法发现以0x00结尾text中的最后一个0x00字节消失tbmi_hash结构:由pgloader在pgsql端按照
2.3.中mysql://源下默认的CASTING所创建:9.1. 由于pgsql没有mysql特有(
isosql不要求)带unsigned修饰的的int类型 https://stackoverflow.com/questions/20810134/why-unsigned-integer-is-not-available-in-postgresql/59802732#59802732 正如同m$ft精神mvp最爱的中小实体企业c#工控上位机人上壬https://z.n0099.net/#narrow/near/94726 除非使用扩展 https://github.com/petere/pguint 因而导致该列上限值从$2^{64} = 18446744073709551615$ 降到 $2^{63} = 9223372036854775807$
9.2. 实际上所谓的
opencv_imghashlumina37/aiotieba#63 (comment) 之averageHashopen-tbm/c#/imagePipeline/src/Consumer/HashConsumer.cs
Line 19 in 252710b
可运算的正整数语义之类型存储而应直接视作斑点二进制https://en.wikipedia.org/wiki/Binary_large_object9.3.$> 2^{63}$ 的
一键生成找出所有存在值bigint unsigned列的mysqlREGEXP部分同
2.1.9.4.
类比
7.1.5.重新导入该表Comment is too long (maximum is 65536 characters)