チームは新しいフレームを交換しました。すべての新しいビジネスでは、新しいフレームワークと新しいデータベースである MySQL が使用されます。 当社は以前から Oracle を使用しており、各種注文番号、シリアル番号、バッチ番号などはすべて Oracle のシーケンスによって直接提供されるデジタルシリアル番号です。データベースが MySQL に変更されたため、古い方法は適用できなくなったことは明らかです。 新しいものを書く必要があります: • 分散シーンの使用 •特定の同時実行要件を満たす いくつかの関連情報を見つけたところ、この点に関して MySQL の実装はデータベース レコードの原則に基づいており、その値が常に更新されていることがわかりました。そして、実装ソリューションのほとんどは関数を使用します。 インターネットからコードを貼り付けます: mysql関数に基づく実装 テーブル構造 テーブル `t_sequence` を作成します ( `sequence_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'シーケンス名' , `value` int(11) NULL DEFAULT NULL COMMENT '現在の値' , 主キー (`sequence_name`) ) エンジン=InnoDB デフォルト文字セット=utf8 COLLATE=utf8_general_ci ROW_FORMAT=コンパクト ; 次の値を取得する CREATE DEFINER = `root`@`localhost` FUNCTION `nextval`(sequence_name varchar(64)) 戻り値 int(11) 始める 現在の整数を宣言します。 電流を 0 に設定します。 t_sequence t を更新し、t.value = t.value + 1 を設定します。ここで、t.sequence_name = sequence_name; t.sequence_name = sequence_name の場合、 t_sequence t から t.value を現在の値に選択します。 電流を返す。 終わり; 同時実行シナリオでは問題が発生する可能性があります。ビジネス レイヤーでロックを追加できますが、分散シナリオではこれが保証されず、効率は高くなりません。 自分で実装する、Java版 原理: • レコードを読み取り、0~100などのデータセグメントをキャッシュし、レコードの現在の値を0~100に変更します。 • データベースの更新楽観ロック、再試行を許可 • キャッシュからデータを読み取り、使い切った後にデータベースから読み取ります 冗談抜きで、コードは次のとおりです。 Javaベースの実装 テーブル構造 各更新でSEQ_VALUEがSEQ_VALUE+STEPに設定されます。 テーブル `t_pub_sequence` を作成します ( `SEQ_NAME` varchar(128) CHARACTER SET utf8 NOT NULL COMMENT 'シーケンス名', `SEQ_VALUE` bigint(20) NOT NULL COMMENT '現在のシーケンス値', `MIN_VALUE` bigint(20) NOT NULL COMMENT '最小値', `MAX_VALUE` bigint(20) NOT NULL COMMENT '最大値', `STEP` bigint(20) NOT NULL COMMENT '各回に取得される値の数', `TM_CREATE` datetime NOT NULL COMMENT '作成時刻', `TM_SMP` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '変更時刻', 主キー (`SEQ_NAME`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='シリアル番号生成テーブル'; シーケンスインターフェース /** * <p></p> * @著者 coderzl * @タイトルMysqlシーケンス * @Description MySQLデータベースに基づくシーケンス * @date 2017/6/6 23:03 */ パブリックインターフェースMysqlSequence { /** * <p> * 指定されたシーケンスのシリアル番号を取得します * </p> * @param seqName シーケンス名* @return String シーケンス番号*/ パブリック String nextVal(String seqName); } シーケンス間隔 最小から最大までのシーケンスをローカルにキャッシュするために使用されます /** * <p></p> * * @著者 coderzl * @タイトルシーケンス範囲 * @Description シーケンス間隔、シーケンスをキャッシュするために使用される * @date 2017/6/6 22:58 */ @データ パブリッククラスSequenceRange { プライベートファイナルロングミニット; プライベートファイナルロングマックス; /** */ プライベート最終AtomicLong値; /** 制限を超えていますか? */ プライベート volatile ブール値 over = false; /** * 工事。 * * @param 最小値 * @param 最大値 */ パブリックシーケンス範囲(long min, long max) { min = min; this.max = 最大値; this.value = 新しい AtomicLong(min); } /** * <p>取得と増分</p> * * @戻る */ パブリックlong getAndIncrement() { 長い現在の値 = value.getAndIncrement(); 現在の値 > 最大値の場合 以上 = true; -1 を返します。 } 現在の値を返します。 } } ボ 対応するデータベースレコード @データ パブリッククラスMysqlSequenceBo { /** * シーケンス名 */ プライベート文字列 seqName; /** * 現在の値 */ プライベート Long seqValue; /** * 最小値 */ プライベート Long minValue; /** * 最大値 */ プライベート Long maxValue; /** * 毎回取得される値の数 */ プライベートロングステップ; /** */ プライベート日付tmCreate; /** */ プライベート日付 tmSmp; パブリックブール検証(){ //いくつかの簡単なチェック。たとえば、現在の値は最大値と最小値の間にある必要があります。ステップ値は、max と minif の差よりも大きくすることはできません (StringUtil.isBlank(seqName) || minValue < 0 || maxValue <= 0 || step <= 0 || minValue >= maxValue || maxValue - minValue <= step ||seqValue < minValue || seqValue > maxValue ) { false を返します。 } true を返します。 } } ダオ 追加、削除、変更、確認、実際には変更とクエリを使用します パブリックインターフェースMysqlSequenceDAO { /** * */ パブリック int createSequence(MysqlSequenceBo bo); public int updSequence(@Param("seqName") String seqName、@Param("oldValue") long oldValue、@Param("newValue") long newValue); パブリック int delSequence(@Param("seqName") String seqName); パブリック MysqlSequenceBo getSequence(@Param("seqName") String seqName); パブリック リスト<MysqlSequenceBo> getAll(); } マッパー <?xml バージョン="1.0" エンコーディング="UTF-8" ?> <!DOCTYPE マッパー PUBLIC "-//mybatis.org//DTD マッパー 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <マッパー名前空間="com.xxxxx.core.sequence.impl.dao.MysqlSequenceDAO" > <resultMap id="BaseResultMap" タイプ="com.xxxxx.core.sequence.impl.MysqlSequenceBo" > <結果列="SEQ_NAME" プロパティ="seqName" jdbcType="VARCHAR" /> <結果列="SEQ_VALUE" プロパティ="seqValue" jdbcType="BIGINT" /> <結果列="MIN_VALUE" プロパティ="minValue" jdbcType="BIGINT" /> <結果列="MAX_VALUE" プロパティ="maxValue" jdbcType="BIGINT" /> <結果列="STEP" プロパティ="step" jdbcType="BIGINT" /> <結果列="TM_CREATE" プロパティ="tmCreate" jdbcType="TIMESTAMP" /> <結果列="TM_SMP" プロパティ="tmSmp" jdbcType="TIMESTAMP" /> </結果マップ> <delete id="delSequence" パラメータタイプ="java.lang.String" > t_pub_sequence から削除 SEQ_NAME = #{seqName,jdbcType=VARCHAR} の場合 </削除> <挿入 id="createSequence" パラメータタイプ="com.xxxxx.core.sequence.impl.MysqlSequenceBo" > t_pub_sequence に挿入 (SEQ_NAME、SEQ_VALUE、MIN_VALUE、MAX_VALUE、STEP、TM_CREATE) 値(#{seqName,jdbcType=VARCHAR}、#{seqValue,jdbcType=BIGINT}、 #{最小値、jdbcType=BIGINT}、#{最大値、jdbcType=BIGINT}、#{ステップ、jdbcType=BIGINT}、 今()) </挿入> <update id="updSequence" パラメータタイプ="com.xxxxx.core.sequence.impl.MysqlSequenceBo" > t_pub_sequence を更新 SEQ_VALUE = #{newValue,jdbcType=BIGINT} を設定します ここで、SEQ_NAME = #{seqName,jdbcType=VARCHAR}、SEQ_VALUE = #{oldValue,jdbcType=BIGINT} です。 </更新> <select id="getAll" resultMap="BaseResultMap" > SEQ_NAME、SEQ_VALUE、MIN_VALUE、MAX_VALUE、STEP を選択 t_pub_sequence から </選択> <select id="getSequence" resultMap="BaseResultMap" > SEQ_NAME、SEQ_VALUE、MIN_VALUE、MAX_VALUE、STEP を選択 t_pub_sequence から SEQ_NAME = #{seqName,jdbcType=VARCHAR} の場合 </選択> </マッパー> インターフェースの実装 @リポジトリ("mysqlシーケンス") パブリッククラスMysqlSequenceImplはMysqlSequenceを実装します{ オートワイヤード プライベート MysqlSequenceFactory mysqlSequenceFactory; /** * <p> * 指定されたシーケンスのシリアル番号を取得します * </p> * * @param seqName シーケンス名 * @return String シーケンス番号 * @author coderzl */ @オーバーライド パブリック文字列 nextVal(文字列 seqName) { Objects.toString(mysqlSequenceFactory.getNextVal(seqName)) を返します。 } } 工場 工場はたった2つのことだけをやった •サービスが開始したら、データベース内のすべてのシーケンスを初期化します[完全なシーケンス間隔キャッシュ] • シーケンスの次の値を取得する @成分 パブリッククラスMysqlSequenceFactory { プライベート最終ロック lock = new ReentrantLock(); /** */ プライベート Map<String,MysqlSequenceHolder> holderMap = 新しい ConcurrentHashMap<>(); オートワイヤード プライベートMysqlSequenceDAO msqlSequenceDAO; /** 単一シーケンスの楽観的ロック更新の初期化に失敗した場合の再試行回数*/ @Value("${seq.init.retry:5}") プライベート int initRetryNum; /** 単一シーケンス間隔の失敗した楽観的ロック更新の再試行回数*/ @Value("${seq.get.retry:20}") プライベート int getRetryNum; @投稿コンストラクト プライベートvoid init(){ //すべてのシーケンスを初期化する すべて初期化します。 } /** * <p> テーブル内のすべてのシーケンスをロードし、初期化を完了します</p> * @戻り値:void * @著者 coderzl */ プライベートvoid initAll(){ 試す { ロック。ロック(); リスト<MysqlSequenceBo> boList = msqlSequenceDAO.getAll(); boList == nullの場合{ 新しい IllegalArgumentException をスローします ("シーケンス レコードが null です!"); } (MysqlSequenceBo bo : boList) の場合 { MysqlSequenceHolder ホルダー = 新しい MysqlSequenceHolder(msqlSequenceDAO、bo、initRetryNum、getRetryNum); ホルダーを初期化します。 ホルダーマップに、ホルダーのSeqName を格納します。 } }ついに { ロックを解除します。 } } /** * <p> </p> * @param シーケンス名 * @戻り値 long * @著者 coderzl */ パブリックlong getNextVal(String seqName){ MysqlSequenceHolder ホルダー = holderMap.get(seqName); ホルダーがnullの場合 試す { ロック。ロック(); ホルダー = holderMap.get(seqName); ホルダーが null の場合 holder.getNextVal() を返します。 } MysqlSequenceBo bo = msqlSequenceDAO.getSequence(seqName); ホルダー = 新しい MysqlSequenceHolder(msqlSequenceDAO、bo、initRetryNum、getRetryNum); ホルダーを初期化します。 holderMap.put(seqName、ホルダー); }ついに { ロックを解除します。 } } holder.getNextVal() を返します。 } } 単一シーケンスホルダー • init() 初期化にはパラメータの検証、データベースレコードの更新、シーケンス間隔の作成が含まれます •getNextVal() 次の値を取得する パブリッククラスMysqlSequenceHolder { プライベート最終ロック lock = new ReentrantLock(); /** シーケンス名 */ プライベート文字列 seqName; /** シーケンスDao */ プライベートMysqlSequenceDAOシーケンスDAO; プライベートMysqlSequenceBoシーケンスBo; /** */ プライベート SequenceRange シーケンス範囲; /** 初期化するかどうか */ プライベート volatile ブール値 isInitialize = false; /** シーケンス初期化の再試行回数*/ プライベート int initRetryNum; /** シーケンスは再試行回数を取得します*/ プライベート int getRetryNum; /** * <p>コンストラクタ</p> * @タイトルMysqlシーケンスホルダー * @param シーケンスDAO * @param シーケンスBo * @param initRetryNum 初期化中にデータベース更新が失敗した後の再試行回数 * @param getRetryNum nextVal取得時にデータベース更新が失敗した後の再試行回数 * @return * @著者 coderzl */ パブリック MysqlSequenceHolder(MysqlSequenceDAO シーケンスDAO、MysqlSequenceBo シーケンスBo、int initRetryNum、int getRetryNum) { this.sequenceDAO = シーケンスDAO; this.sequenceBo = シーケンスBo; initRetryNum は、次の式で定義されます。 戻り値: if(シーケンスBo != null) this.seqName = シーケンスBo.getSeqName(); } /** * <p> 初期化 </p> * @タイトル初期化 * @パラメータ * @戻り値:void * @著者 coderzl */ パブリックvoid init(){ 初期化が true の場合 throw new SequenceException("[" + seqName + "] MysqlSequenceHolder が初期化されました"); } シーケンスDAO == nullの場合{ 新しい SequenceException をスローします ("[" + seqName + "] シーケンスDao は null です"); } seqName == null || seqName.trim().length() == 0 の場合 { throw new SequenceException("[" + seqName + "] シーケンス名はnullです"); } シーケンスBo == nullの場合{ 新しい SequenceException をスローします ("[" + seqName + "] シーケンス Bo は null です"); } シーケンスBoを検証する場合(){ 新しい SequenceException をスローします ("[" + seqName + "] シーケンス Bo の検証が失敗しました。BO:"+sequenceBo); } // シーケンスを初期化する 試す { シーケンスレコードを初期化します。 } キャッチ (シーケンス例外 e) { eを投げる; } 初期化 = true; } /** * <p>次のシーケンス番号を取得します</p> * @タイトル getNextVal * @パラメータ * @戻り値 long * @著者 coderzl */ パブリックlong getNextVal(){ if(isInitialize == false){ throw new SequenceException("[" + seqName + "] MysqlSequenceHolder が初期化されていません"); } シーケンス範囲が null の場合 throw new SequenceException("[" + seqName + "] シーケンス範囲がnullです"); } 長い curValue = シーケンス範囲.getAndIncrement(); if(現在の値 == -1){ 試す{ ロック。ロック(); curValue = シーケンス範囲の取得と増分(); もし現在の値が -1 ならば curValue を返します。 } シーケンス範囲 = retryRange(); curValue = シーケンス範囲の取得と増分(); }ついに { ロックを解除します。 } } curValue を返します。 } /** * <p> 現在のレコードを初期化します </p> * @Title initSequenceRecord * @説明 * @param シーケンスBo * @戻り値:void * @著者 coderzl */ プライベートvoid initSequenceRecord(MysqlSequenceBo シーケンスBo){ //楽観的ロックは、限られた回数内でデータベースレコードを更新します for(int i = 1; i < initRetryNum; i++){ //クエリbo MysqlSequenceBo curBo = シーケンスDAO.getSequence(シーケンスBo.getSeqName()); if(curBo == null){ throw new SequenceException("[" + seqName + "] 現在のシーケンスBoはnullです"); } もし(!curBo.validate()){ throw new SequenceException("[" + seqName + "] 現在のシーケンスBoの検証に失敗しました"); } //現在の値を変更します long newValue = curBo.getSeqValue()+curBo.getStep(); //現在の値を確認する if(!checkCurrentValue(newValue,curBo)){ 新しい値 = 現在の値をリセットします(curBo); } int 結果 = sequenceDAO.updSequence(sequenceBo.getSeqName(),curBo.getSeqValue(),newValue); if(結果 > 0){ シーケンス範囲 = 新しいシーケンス範囲(curBo.getSeqValue()、新しい値 - 1); curBo.setSeqValue(新しい値); this.sequenceBo = curBo; 戻る; }それ以外{ 続く; } } // 指定された回数内に更新が失敗した場合は、例外がスローされます。 throw new SequenceException("[" + seqName + "] sequenceBo update error"); } /** * <p> 新しい値が正当かどうか、新しい現在の値が最大値と最小値の間にあるかどうかを確認します</p> * @param curValue * @param curBo * @return ブール値 * @著者 coderzl */ プライベートブール値 checkCurrentValue(long curValue,MysqlSequenceBo curBo){ curBo.getMinValue() の場合、curBo.getMaxValue() は curValue より小さくなります。 true を返します。 } false を返します。 } /** * <p> シーケンスの現在の値をリセットします。現在のシーケンスが最大値に達すると、最小値から再び開始されます。</p> * @タイトル リセット現在の値 * @param curBo * @戻り値 long * @著者 coderzl */ プライベートロングリセット現在の値(MysqlSequenceBo curBo){ curBo.getMinValue() を返します。 } /** * <p> キャッシュ間隔が使い果たされると、データベースレコードを再度読み取り、新しいシーケンスセグメントをキャッシュします</p> * @Title 再試行範囲 * @param シーケンス範囲 * @著者 coderzl */ プライベートシーケンス範囲 retryRange(){ for(int i = 1; i < getRetryNum; i++){ //クエリbo MysqlSequenceBo curBo = シーケンスDAO.getSequence(シーケンスBo.getSeqName()); if(curBo == null){ throw new SequenceException("[" + seqName + "] 現在のシーケンスBoはnullです"); } もし(!curBo.validate()){ throw new SequenceException("[" + seqName + "] 現在のシーケンスBoの検証に失敗しました"); } //現在の値を変更します long newValue = curBo.getSeqValue()+curBo.getStep(); //現在の値を確認する if(!checkCurrentValue(newValue,curBo)){ 新しい値 = 現在の値をリセットします(curBo); } int 結果 = sequenceDAO.updSequence(sequenceBo.getSeqName(),curBo.getSeqValue(),newValue); 結果 > 0 の場合 シーケンス範囲 = 新しいシーケンス範囲(curBo.getSeqValue()、新しい値 - 1); curBo.setSeqValue(新しい値); this.sequenceBo = curBo; シーケンス範囲を返します。 }それ以外{ 続く; } } 新しい SequenceException をスローします ("[" + seqName + "] シーケンスBo 更新エラー"); } } 要約する • サービスが再起動または異常になった場合、現在のサービスによってキャッシュされ未使用のシーケンスは失われます •分散シナリオでは、複数のサービスが同時に初期化されるときやシーケンスを再取得するときに、楽観的ロックによってそれらが互いに競合しないことが保証されます。サービス A は 0 ~ 99、サービス B は 100 ~ 199 というように取得します。 •シーケンスを頻繁に取得する場合は、ステップ値を増やすとパフォーマンスが向上する可能性があります。しかし同時に、サービスが異常な場合、より多くのシーケンスが失われます • データベース内のシーケンスのいくつかの属性値(ステップ、最大値など)を変更すると、次回データベースから取得するときに新しいパラメータが有効になります。 •シーケンスは限られた数のシーケンス番号(最大 max-min まで)のみを提供します。最大値に達すると、ループして最初から開始されます。 •シーケンスはループするため、最大値に達すると、再度取得しても一意ではなくなります。ビジネスシリアル番号と接合時間を作成するには、シーケンスを使用することをお勧めします。例: 20170612235101+シリアル番号 ビジネスIDの結合方法 @サービス パブリッククラスJrnGeneratorService { プライベート静的最終文字列 SEQ_NAME = "T_SEQ_TEST"; /** シーケンスサービス */ オートワイヤード プライベート MySqlSequence mySqlSequence; パブリック文字列generateJrn() { 試す { 文字列シーケンス = mySqlSequence.getNextValue(SEQ_NAME); シーケンス = leftPadding(シーケンス、8); カレンダーcalendar = Calendar.getInstance(); SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); 文字列 nowdate = sDateFormat.format(calendar.getTime()); nowdate.substring(4, nowdate.length()); String jrn = nowdate + シーケンス + RandomUtil.getFixedLengthRandom(6); //10 桁の時刻 + 8 桁のシーケンス + 6 桁の乱数 = 24 桁のシリアル番号 return jrn; } キャッチ (例外 e) { //やるべきこと } } プライベート文字列leftPadding(文字列seq,int len){ 文字列 res = ""; 文字列 str = ""; seq.length()<len)の場合{ for(int i=0;i<len-seq.length();i++){ str +="0"; } } res = str+seq; res を返します。 } } 私が皆さんに共有したいのは、上記の MySQL ベースのシーケンス実装方法です。これが皆さんの参考になれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。 以下もご興味があるかもしれません:
|
<<: 実行中の Docker コンテナにボリュームを動的に追加する方法
>>: Reactの3つの主要属性におけるpropsの使用の詳細な説明
まず、次の質問について考えてみましょう。このような膨大な量のデータをデータベースに挿入するには、通常...
1. mysqlをインストールします。 udo apt-getでmysql-serverをインストー...
序文CSS グリッドは通常、さまざまなフレームワークにバンドルされていますが、実際のビジネス ニーズ...
プレビューアドレス: https://ovsexia.gitee.io/leftfixed/ htm...
シナリオ: ページAがページBを開くと、ページBで操作した後、ページAは変更されたデータを同期する必...
今日は、ネイティブ JS で実装された画像マーキー効果を紹介します。効果は次のとおりです。 実装され...
目次解決策1: レプリカを再構築する前提条件アドバンテージ欠点手順マスター奴隷解決策2: データ修復...
序文MySQL では、server-id を使用してデータベース インスタンスを一意に識別し、それを...
この記事では、Linux で PHP curl 拡張機能をインストールする方法について説明します。ご...
目次1. 現在のデータベース内のテーブルを表示する2. テーブルを作成する3. 指定されたテーブル構...
最終的な解決策は最後の写真にありますリモート データベース ( Linux システム) に接続したと...
目次MySQLデータベースの名前を変更する方法最初の方法: データベースの名前を変更することは非推奨...
目次1. 準備1. 環境を整える2. インストール方法3. ネットワークカードの構成2. インストー...
設置環境WIN10 VMware Workstation Pro 15.0.0 ビルド 101344...
今日、会社のプロジェクトでは docker を設定する必要があります。Windows に正常にインス...