MySQL が Json 内部フィールドを抽出し、それらを数値としてダンプしたことを思い出してください。

MySQL は Json 内部フィールドを抽出し、それらを数値としてダンプします

これは単なるデータ移行の統計であり、データ量は大きくありません。問題は、いくつかの中間ステップの処理と考慮です。

SQLの最適化やインデックスの最適化に関する内容はありませんので、軽く読んでください。

画像-20210708172637941

バックグラウンド

利用者眼科属性表のレコード数は約986wで、約29wのレコードの属性値(json形式)の8フィールドを数値にパースし、チャート分析用の統計表のレコードとしてダンプするのが目的です。

以下の構造とデータのほとんどは私が作成したものなので、真剣に受け止めるべきではありません。

ユーザー眼科属性テーブル構造は次のとおりです。

CREATE TABLE `property` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ownerId` int(11) NOT NULL COMMENT '记录ID或者模板ID',
  `ownerType` tinyint(4) NOT NULL COMMENT '类型。0:记录 1:模板',
  `recorderId` bigint(20) NOT NULL DEFAULT '0' COMMENT '记录者ID',
  `userId` bigint(20) NOT NULL DEFAULT '0' COMMENT '用户ID',
  `roleId` bigint(20) NOT NULL DEFAULT '0' COMMENT '角色ID',
  `type` tinyint(4) NOT NULL COMMENT '字段类型。0:文本 1:备选项 2:时间 3:图片 4:ICD10 9:新图片',
  `name` varchar(128) NOT NULL DEFAULT '' COMMENT '字段名称',
  `value` mediumtext NOT NULL COMMENT '字段值',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idxOwnerIdOwnerTypeNameType` (`ownerType`,`ownerId`,`name`,`type`) USING BTREE,
  KEY `idxUserIdRoleIdRecorderIdName` (`userId`,`roleId`,`recorderId`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='属性';

問題分析

1.属性値はJson形式であり、Json操作関数で処理する必要があります

属性値が Json 形式のため、以下のようになります。より大きなJsonですが、必要なフィールド値は8つだけで、抽出されてさまざまな統計指標に分類されます。

{
    
       ......
    "sight": {
    
    
        "nakedEye": {
    
    
            "left": "0.9",
            "right": "0.6"
        },
        "correction": {
    
    
            "left": "1",
            "right": "1"
        }
    },
    ......
    "axialLength": {
    
    
        "left": "21",
        "right": "12"
    },
    "korneaRadius": {
    
    
        "left": "34",
        "right": "33"
    },
    ......
}

したがって、Json 操作関数: を使用する必要がありますjson_extract(value,'$.key1.key2')

ただし、この関数によって抽出される値には "" が付いていることに注意してください。たとえば、json_extract(value,'$.sight.nakedEye.left')上記のレコードを実行した結果は です"22"。また、フィールド値が空の文字列で、結果が である可能性もあります""

したがって、replace関数""結果の を削除する必要があり、最後にフィールドを抽出する式は次のとおりですreplace(json_extract(value,'$.sight.nakedEye.left'),'"','')

フィールドが存在しない場合、結果は NULL です。外側の視界が存在しないか、内側の左側が存在しません。

2. フィールドの内容が標準化されておらず、ごちゃごちゃしている

理想的には、標準の数値を入力し、上記の手順の後、それらを抽出して新しいテーブルに直接インポートできます。

しかし、現実は残酷で、埋められたものはめちゃくちゃです。例えば:

  • Number + Remarks: 1(配合欠佳), 1-\+(これは、高いまたは低いという意味だと思います)
  • 数値 + 単位: 上記と同様、1mm
  • 複数の値または間隔: 22.52/42.45,1-5
  • プレーンテキストの説明: 不配合,无法记录
  • テキストと数字の混合説明: 较上次增长 10<1小于1BD234/KD23

画像

仕方なく商品や商売の状況を調べてみましたが、幸い4,000件強と少なく、ざっとスキャンしただけで分かる範囲です。次の解決策が見つかりました。

  • 数字で始まる: 数字の始まりは正しく記録されたデータです。テキストの説明は省略してください
  • 複数の値または間隔: 最初の数値を取る
  • プレーンテキスト: データがないことを示し、除外します
  • テキストと数字の混合: 特定の問題を詳細に分析し、他の問題に対処した後にどれだけ残っているかを確認します

どうやってするの?

ステップ 1: 通常の数値データと空のデータを除外する

WHERE `nakedEyeLeft` REGEXP '[^0-9.]' = 1 // 这个已经可以排除 null 了
	AND `nakedEyeLeft` != ''

ステップ 2: 数値が含まれていない場合は、NULL または空の文字列に設定します。

SET nakedEyeLeft = IF(nakedEyeLeft NOT regexp '[0-9]', '', nakedEyeLeft)

ステップ 3: 数値の先頭にあるデータの最初の値を抽出する

SET nakedEyeLeft = IF((nakedEyeLeft + 0 = 0), nakedEyeLeft, nakedEyeLeft + 0)

組み合わせて

SET nakedEyeLeft = IF(nakedEyeLeft NOT regexp '[0-9]''', '', 
                      IF((nakedEyeLeft + 0 = 0), nakedEyeLeft, nakedEyeLeft + 0))
WHERE `nakedEyeLeft` REGEXP '[^0-9.]' = 1 // 这个已经可以排除 null 了
	AND `nakedEyeLeft` != ''

PS: フィールドを処理するための SQL は単純に見えますが、一度に 8 つのフィールドをバッチで処理するため、組み合わせは非常に長くなります。

間違ったフィールドを書き込まないように注意してください。

最後に残ったのは 4 番目のカテゴリです。テキストと数字が混在し、40 を超える項目があります。

シンプルに見えるものもあり、 、 などの正規表現で自動化できます<1小于1

レコードの成長値は、計算のために最後のレコードを見つける必要があります: 较上次增长 10.

残りの複雑なものについては、次のような利用可能なデータを抽出するために人間の処理が必要です。BD234/KD23

ここを見ている皆さんも少し面倒くさいと思うかどうかわかりませんか?

私も歯を食いしばってやっていると思っていたのですが、直接0に加工したとのことでした。 その時点で0であることが判明した場合は、ページを通じて再度保存することができます。

画像-20210708160701962

先頭の数字かどうかを直接+0で判断する必要はなく、先頭の数字なら先頭の数字のまま、そうでなければ=0。

次に、最終的なデータ形式の SQL:

UPDATE property 
SET nakedEyeLeft = IF(nakedEyeLeft NOT regexp '[0-9]''', '', nakedEyeLeft + 0)
WHERE `nakedEyeLeft` REGEXP '[^0-9.]' = 1 // 这个已经可以排除 null 了
	AND `nakedEyeLeft` != '';
3. コンテンツとフォーマットを抽出する必要がありますが、レコードにはまだ 900w+ があり、遅すぎます。

プロパティ テーブルには 900w+ のデータがあり、必要な記録条件、 、 のみが既知nameあり、既存のインデックスにヒットできません。ownerTypetype

直接検索する場合は、完全なテーブル スキャンに加えて、データの抽出と書式設定が直接行われます。さらに、他のテーブルを関連付けて、統計指標の他のフィールドを補足する必要があります。

この場合、統計表を直接インポートすると、2 つの表 + 関連する表が長時間ロックされ、その間は変更や挿入ができなくなり、現実的ではありません。

走査線数を減らす

方法 1: name、にインデックスを追加しownerTypetypeスキャン レコードを 20w に減らします。

しかし、問題は、900w のデータがインデックス化されており、インデックスが使い果たされると削除する必要があることです (ビジネスの状況では必要ないため)。これにより、2 つの変動が発生します。

フォローアップ処理のロック テーブルの長さと相まって、問題は依然として非常に大きなものです。

方法 2: 駆動テーブルとしてレコード数の少ないテーブルを使用すると、このテーブルをターゲット テーブルに関連付けることができます。

CREATE TABLE `property` (
  `ownerId` int(11) NOT NULL COMMENT '记录ID或者模板ID',
  `ownerType` tinyint(4) NOT NULL COMMENT '类型。0:记录 1:模板',
  `type` tinyint(4) NOT NULL COMMENT '字段类型。0:文本 1:备选项 2:时间 3:图片 4:ICD10 9:新图片',
  `name` varchar(128) NOT NULL DEFAULT '' COMMENT '字段名称',
  `value` mediumtext NOT NULL COMMENT '字段值',
    省略其他字段
  UNIQUE KEY `idxOwnerIdOwnerTypeNameType` (`ownerType`,`ownerId`,`name`,`type`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='属性';

テーブルはownerId、レコード テーブルに加えて、前の条件name, ownerType,に関連付けることができるtypeため、単にヒットし、 ``idxOwnerIdOwnerTypeNameType (ownerType ,ownerId ,name ,type ).

CREATE TABLE `medicalrecord` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '记录名称',
  `type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '记录类型。',
    省略其他字段
  KEY `idxName` (`name`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COMMENT='记录';

レコード テーブルはname='眼科记录'インデックスにidxName。スキャンされた行の数はわずか 2w で、属性テーブル 29w を加えたものです。スキャンされた行の最終的な数は約 30w にすぎません。これは、フル テーブル スキャン属性テーブルの 30 分の 1 です。! ! .

データの抽出と書式設定のためのテーブル ロック期間を回避する

8つのフィールドがあるため、各フィールドを抽出して整形する必要があり、途中で判断が必要です。このように、1 つの SQL 内の同じ抽出およびフォーマット操作が複数回実行されます。

したがって、このような問題を回避するために、抽出および書式設定の結果を一時的に格納するための中間テーブルが必要です。

CREATE TABLE `propertytmp` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
   `value` mediumtext NOT NULL COMMENT '字段值',
  `nakedEyeLeft` varchar(255) DEFAULT NULL COMMENT '视力-裸眼-左眼',
  `nakedEyeRight` varchar(255) DEFAULT NULL COMMENT '视力-裸眼-右眼',
  `correctionLeft` varchar(255) DEFAULT NULL COMMENT '视力-矫正-左眼',
  `correctionRight` varchar(255) DEFAULT NULL COMMENT '视力-矫正-右眼',
  `axialLengthLeft` varchar(255) DEFAULT NULL COMMENT '眼轴长度-左眼',
  `axialLengthRight` varchar(255) DEFAULT NULL COMMENT '眼轴长度-右眼',
  `korneaRadiusLeft` varchar(255) DEFAULT NULL COMMENT '角膜曲率-左眼',
  `korneaRadiusRight` varchar(255) DEFAULT NULL COMMENT '角膜曲率-右眼',
  `updated` datetime NOT NULL COMMENT '更新时间',
  `deleted` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4;

最初にデータをテーブルにインポートし、これに基づいて抽出してからフォーマットします。

最終実行結果の比較

データインポートの比較

結果: フル テーブル スキャン属性テーブル インポート中間テーブル (40 秒)、属性テーブルの新しいインデックス + インポート (6 秒 + 3 秒)、関連付けインポート (1.4 秒)。

他のテーブルを関連付ける必要があるため、予測されたほど理想的ではありません。

中間テーブル データ抽出: 7.5 秒
UPDATE `propertytmp` 
SET nakedEyeLeft = REPLACE(json_extract(value,'$.sight.axialLength.left'),'"',''),
nakedEyeLeft = REPLACE(json_extract(value,'$.sight.nakedEye.left'),'"',''),
nakedEyeRight = REPLACE(json_extract(value,'$.sight.nakedEye.right'),'"',''),
correctionLeft = REPLACE(json_extract(value,'$.sight.correction.left'),'"',''),
correctionRight = REPLACE(json_extract(value,'$.sight.correction.right'),'"',''),
axialLengthLeft = REPLACE(json_extract(value,'$.axialLength.left'),'"',''),
axialLengthRight = REPLACE(json_extract(value,'$.axialLength.right'),'"',''),
korneaRadiusLeft = REPLACE(json_extract(value,'$.korneaRadius.left'),'"',''),
korneaRadiusRight = REPLACE(json_extract(value,'$.korneaRadius.right'),'"','');
中間テーブル データのフォーマット: 2.3 秒

通常の判定は思ったより早いです。

UPDATE propertytmp 
SET nakedEyeLeft = IF(nakedEyeLeft NOT REGEXP '[0-9]' AND nakedEyeLeft != '', '', nakedEyeLeft + 0), 
nakedEyeRight = IF(nakedEyeRight NOT REGEXP '[0-9]' AND nakedEyeRight != '', '', nakedEyeRight + 0), 
correctionLeft = IF(correctionLeft NOT REGEXP '[0-9]' AND correctionLeft != '', '', correctionLeft + 0),
correctionRight = IF(correctionRight NOT REGEXP '[0-9]' AND correctionRight != '', '', correctionRight + 0),
axialLengthLeft = IF(axialLengthLeft NOT REGEXP '[0-9]' AND axialLengthLeft != '', '', axialLengthLeft + 0),
axialLengthRight = IF(axialLengthRight NOT REGEXP '[0-9]' AND axialLengthRight != '', '', axialLengthRight + 0),
korneaRadiusLeft = IF(korneaRadiusLeft NOT REGEXP '[0-9]' AND korneaRadiusLeft != '', '', korneaRadiusLeft + 0),
korneaRadiusRight = IF(korneaRadiusRight NOT REGEXP '[0-9]' AND korneaRadiusRight != '', '', korneaRadiusRight + 0)
WHERE (`nakedEyeLeft` REGEXP '[^0-9.]' = 1
       AND `nakedEyeLeft` != '')
  OR (`nakedEyeRight` REGEXP '[^0-9.]' = 1
      AND `nakedEyeRight` != '')
  OR (`correctionLeft` REGEXP '[^0-9.]' = 1
      AND `correctionLeft` != '')
  OR (`correctionRight` REGEXP '[^0-9.]' = 1
      AND `correctionRight` != '')
  OR (`axialLengthLeft` REGEXP '[^0-9.]' = 1
      AND `axialLengthLeft` != '')
  OR (`axialLengthRight` REGEXP '[^0-9.]' = 1
      AND `axialLengthRight` != '')
  OR (`korneaRadiusLeft` REGEXP '[^0-9.]' = 1
      AND `korneaRadiusLeft` != '')
  OR (`korneaRadiusRight` REGEXP '[^0-9.]' = 1
      AND `korneaRadiusRight` != '');
統計指標の中間テーブル

実際に統計指標表をインポートする際には、空のデータを除外し、他の表を関連付けて補足する必要があるためです。

指標テーブルへの影響を軽減するために、指標テーブルの中間テーブルを構築し、構造を完全に一致させ、ID の自己インクリメントを対象テーブル + 10000 にします。

属性中間テーブルのデータを指標中間テーブルにインポートし、最終的に直接INSERT ... SELECT FROM、非常に高速になります。

もちろん、このステップは実際には少しやり過ぎですが、ラインの変動を避けるためには注意が必要です。

要約する

これは、データ移行の経験の簡単な記録です。

インデックスの最適化やSQLの最適化に関する内容はありませんが、このようなパフォーマンスやユーザーへの影響については、誰もが考慮する必要があると思います。

おすすめ

転載: blog.csdn.net/jiangxiayouyu/article/details/118578576