ためすう

vector::front, vector::back を使ってみる (C++)

2020-07-08

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> v = {5, 3, 2, 9, 8};

    cout << "v.front(): " << v.front() << endl;
    cout << "v.back(): " << v.back() << endl;

    // インデックス直接指定でもできる
    cout << "v[0]: " << v[0] << endl;
    cout << "v[v.size() - 1]: " << v[v.size() - 1] << endl;
}

出力結果

v.front(): 5
v.back(): 8
v[0]: 5
v[v.size() - 1]: 8

参考

multiset を使ってみる (C++)

2020-07-07

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
using namespace std;

multiset<int> init() {
    multiset<int> s;
    s.insert(2);
    s.insert(3);
    s.insert(2);
    s.insert(1);
    s.insert(2);
    return s;
}

void display(multiset<int> s) {
    for (auto v : s) {
        cout << v << " ";
    }
    cout << endl;
}

int main() {
    multiset<int> s = init();

    cout << "【ケース1】" << endl;
    cout << "変更前" << endl;
    display(s);

    // 3つとも消えてしまう
    s.erase(2);
    cout << "変更後" << endl;
    display(s);
    cout << "---" << endl;

    cout << "【ケース2】" << endl;
    cout << "変更前" << endl;
    s = init();
    display(s);

    cout << typeid(s.find(2)).name() << endl;
    s.erase(s.find(2));
    cout << "変更後" << endl;
    display(s);
    cout << "---" << endl;

    cout << "【ケース3】" << endl;
    cout << "変更前" << endl;
    s = init();
    display(s);

    auto iter = s.lower_bound(2);
    cout << typeid(iter).name() << endl;
    s.erase(iter);
    cout << "変更後" << endl;
    display(s);
}

出力結果

【ケース1】
変更前
1 2 2 2 3
変更後
1 3
---
【ケース2】
変更前
1 2 2 2 3
St23_Rb_tree_const_iteratorIiE
変更後
1 2 2 3
---
【ケース3】
変更前
1 2 2 2 3
St23_Rb_tree_const_iteratorIiE
変更後
1 2 2 3

参考

struct のコンストラクタを使ってみる (C++)

2020-07-06

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
using namespace std;

struct person {
    string name;
    int age;

    // コンストラクタを定義
    person(string name, int age): name(name), age(age) {};
};

int main() {
    person p("person name", 20);
    cout << p.name << endl;
    cout << p.age << endl;
}

出力結果

person name
20

ディレクトリごと差分がないかどうかを調べる

2020-07-05

やったこと

拡張子がないファイルを検索します。

調査

ファイル準備

$ ls -lh sample-diff*
sample-diff1:
total 16
-rw-r--r--  1 hoge  staff     4B  7  4 14:21 aaa.rb
-rw-r--r--  1 hoge  staff     9B  7  4 14:21 bbb.rb

sample-diff2:
total 16
-rw-r--r--  1 hoge  staff    21B  7  4 14:25 aaa.rb
-rw-r--r--  1 hoge  staff    26B  7  4 14:24 bbb.rb

※ ファイルの中身は省略しますが、bbb.rb の中身に差分があります。

man diff のオプションを見てみます。

       -r  --recursive
              Recursively compare any subdirectories found.
$ diff -r sample-diff1 sample-diff2
diff -r sample-diff1/bbb.rb sample-diff2/bbb.rb
2c2
< p 'sample-diff1'
---
> p 'sample-diff2'

bbb.rb に差分があることが分かりました。

拡張子がないファイルを検索したい

2020-07-04

やったこと

拡張子がないファイルを検索します。

調査

ファイル準備

$ ls -la sample-file/
total 0
drwxr-xr-x  6 hogehoge  staff  192  7  4 13:39 .
drwxr-xr-x  4 hogehoge  staff  128  7  4 13:52 ..
-rw-r--r--  1 hogehoge  staff    0  7  4 13:38 aaa.rb
-rw-r--r--  1 hogehoge  staff    0  7  4 13:38 bbb
-rw-r--r--  1 hogehoge  staff    0  7  4 13:39 ccc
-rw-r--r--  1 hogehoge  staff    0  7  4 13:39 ddd.php

今回は find を使ってやってみます。

find [検索対象ディレクトリ] -type ファイルタイプ -not -name "検索パターン"

$ find ./sample-file/ -type f -not -name "*.*"
./sample-file//bbb
./sample-file//ccc

ファイル名に拡張子がある を . が存在すると言い換えると、. が存在しないファイルを抽出することになります。

MySQL で ON DUPLICATE KEY UPDATE を使ってたら、カンストした件

2020-06-20

MySQL の INSERT ... ON DUPLICATE KEY UPDATE 構文 は、大量のデータを1度に INSERT, UPDATE するという構文です。

しかし、この構文を使っている箇所について、下記の事象が発生しました。

  1. 意図せず AUTO_INCREMENT が進む
  2. int で保存できる最大値に達する (カンスト)
  3. 新しくデータが保存できなくなる

今回は、1. のところについての対処方法の共有をしたいと思います。

INSERT ... ON DUPLICATE KEY UPDATE 構文 について

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.5.3 INSERT … ON DUPLICATE KEY UPDATE 構文

この構文を使えば、1本のSQLで、INSERT、UPDATE を実行することができます。

意図せず AUTO_INCREMENT が進んでしまった理由を調べていたところ、

UPDATE 文になった場合でも、AUTO_INCREMENT が進むらしいということが分かりました。

それでは、検証を始めます。

確認環境

$ mysql --version
mysql  Ver 14.14 Distrib 5.6.43, for osx10.13 (x86_64) using  EditLine wrapper

検証

準備

CREATE DATABASE test;
CREATE TABLE `tmp_a` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `uniq` varchar(20) NOT NULL,
  `cnt` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx01` (`uniq`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

現在の AUTO_INCREMENT を確認します。

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              1 |
+----------------+
1 row in set (0.00 sec)

INSERT 文が発行される場合 (id カラムに値を指定しない)

変更前

mysql> SELECT * FROM tmp_a;
Empty set (0.00 sec)
mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              1 |
+----------------+
1 row in set (0.00 sec)

実行

mysql> INSERT INTO tmp_a (id, uniq, cnt)
    -> VALUES
    ->     (null,'u1', 1), (null,'u2', 2), (null,'u3', 3)
    -> ON DUPLICATE KEY UPDATE
    ->  uniq=VALUES(`uniq`),
    ->  cnt=VALUES(`cnt`)
    -> ;
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

変更後

データが登録されたことが確認できます。

mysql>  SELECT * FROM tmp_a;
+----+------+-----+
| id | uniq | cnt |
+----+------+-----+
|  1 | u1   |   1 |
|  2 | u2   |   2 |
|  3 | u3   |   3 |
+----+------+-----+
3 rows in set (0.00 sec)

AUTO_INCREMENT も 3 進みました。

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              4 |
+----------------+
1 row in set (0.00 sec)

UPDATE 文が発行される場合 (id カラムに値を指定する)

変更前

mysql>  SELECT * FROM tmp_a;
+----+------+-----+
| id | uniq | cnt |
+----+------+-----+
|  1 | u1   |   1 |
|  2 | u2   |   2 |
|  3 | u3   |   3 |
+----+------+-----+
3 rows in set (0.00 sec)
mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              4 |
+----------------+
1 row in set (0.00 sec)

実行

cnt を 1000 倍にして、更新します。

mysql> INSERT INTO tmp_a (id, uniq, cnt)
    -> VALUES
    ->     (1,'u1', 1000), (2,'u2', 2000), (3,'u3', 3000)
    -> ON DUPLICATE KEY UPDATE
    ->  uniq=VALUES(`uniq`),
    ->  cnt=VALUES(`cnt`)
    -> ;
Query OK, 6 rows affected (0.01 sec)
Records: 3  Duplicates: 3  Warnings: 0

変更後

cnt が更新されました。

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1000 |
|  2 | u2   | 2000 |
|  3 | u3   | 3000 |
+----+------+------+
3 rows in set (0.00 sec)

AUTO_INCREMENT も変更されずそのままでした。

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              4 |
+----------------+
1 row in set (0.01 sec)

これは意図通り。

UPDATE 文が発行される場合 (id カラムに値を指定しない)

変更前

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1000 |
|  2 | u2   | 2000 |
|  3 | u3   | 3000 |
+----+------+------+
3 rows in set (0.00 sec)
mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              4 |
+----------------+
1 row in set (0.01 sec)

実行

mysql> INSERT INTO tmp_a (id, uniq, cnt)
    -> VALUES
    ->     (null,'u1', 1111), (null,'u2', 2222), (null,'u3', 3333)
    -> ON DUPLICATE KEY UPDATE
    ->  uniq=VALUES(`uniq`),
    ->  cnt=VALUES(`cnt`)
    -> ;
Query OK, 6 rows affected (0.01 sec)
Records: 3  Duplicates: 3  Warnings: 0

変更後

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1111 |
|  2 | u2   | 2222 |
|  3 | u3   | 3333 |
+----+------+------+
3 rows in set (0.01 sec)

AUTO_INCREMENT が先に進んでしまいました。

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              7 |
+----------------+
1 row in set (0.00 sec)

ここで少し休憩です。

INSERT ... ON DUPLICATE KEY UPDATE 構文 を使うとき UPDATE する場合、AUTO_INCREMENT を進めないためには、下記カラムの値を指定する必要があることが分かりました。

  • id (AUTO_INCREMENT)
  • uniq (DUPLICATE KEY)

INSERT, UPDATE 文が発行される場合

今回の問題発生箇所での使われ方です。

変更前

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1111 |
|  2 | u2   | 2222 |
|  3 | u3   | 3333 |
+----+------+------+
3 rows in set (0.01 sec)
mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              7 |
+----------------+
1 row in set (0.00 sec)

実行

mysql> INSERT INTO tmp_a (id, uniq, cnt)
    -> VALUES
    ->     (1,'u1', 1000), (2,'u2', 2000), (3,'u3', 3000),
    ->     (null,'u4', 4), (null,'u5', 5), (null,'u6', 6)
    -> ON DUPLICATE KEY UPDATE
    ->  uniq=VALUES(`uniq`),
    ->  cnt=VALUES(`cnt`)
    -> ;
Query OK, 9 rows affected (0.02 sec)
Records: 6  Duplicates: 3  Warnings: 0

変更前

登録、更新されたデータは意図通りでした。

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1000 |
|  2 | u2   | 2000 |
|  3 | u3   | 3000 |
|  7 | u4   |    4 |
|  8 | u5   |    5 |
|  9 | u6   |    6 |
+----+------+------+
6 rows in set (0.00 sec)

6 つ進んでしまいました!!!

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|             13 |
+----------------+
1 row in set (0.01 sec)

どうやら、登録しようとしているデータ全部を更新しなければ、AUTO_INCREMENT が進むようです。

対処方法

id カラムを削除する

DB を利用している側の制約上、削除できない可能性もありますが、

id カラムを削除すれば AUTO_INCREMENT のことを考えなくて良くなります。

INSERT文とUPDATE文を分ける

UPDATE 文のみであれば、AUTO_INCREMENT が更新されないことが分かったので、

SQL を分割するのが良さそうです。

参照渡しを使ってみる (C++)

2020-05-23

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
using namespace std;

void f1(int *a) {
    for (int i = 0; i < 3; i++) {
        a[i] *= 2;
    }
}

void f2(vector<int> &a) {
    for (int i = 0; i < 3; i++) {
        a[i] *= a[i];
    }
}

int main() {
    int a[] {1, 2, 3};
    f1(a);

    vector<int> b = {1, 2, 3};
    f2(b);

    // 関数を通すと値が変わっている
    for (int i = 0; i < 3; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    for (int i = 0; i < 3; i++) {
        cout << b[i] << " ";
    }
    cout << endl;
}

出力結果

2 4 6
1 4 9

参考

AtCoderで緑色になりました! からの、思い出の問題ベスト3 と やってきたこと少々

2020-05-19

2020/05/10 AtCoder Beginner Contest 167 で緑にタッチすることが出来たので、色変記事でも書こうと思います。

(今後、何度か反復横跳びしそうですが、書いちゃいます。)

普段やっていることで、特に変わったことはないので既出の内容かなと思います。

なので、コンテスト中に対峙した記憶に残る問題ベスト3のことから書いてみます。

rating.png

第3位

E - Crested Ibis vs Monster

初めてコンテスト中に DP を使って AC しました。

雰囲気でやったら、サンプルが合ったので、添字をバグらせまくりながらACしました。

そういえば、この時のコンテストで茶色になりました。

恥ずかしながらコンテスト中に DP を使って解けたのは、これっきり… (頑張ります)

第2位

D - Banned K

直前のC問題でド嵌りして、この問題をやるとき時間がほとんどなかったのですが、15m くらいでギリギリ解けました。

(諦めなければ、何とかなることもあるなと思ったりしました。)

ちなみに、この時のコンテストは、パフォーマンスが悪く冷えましたが、 冷えを少なくできたということでこの問題が印象に残っています。

この時、3連続冷え中でした。まあまあ辛かったです。

第1位

C - Sqrt Inequality

はい。まあまあ問題文が短いアレです。コンテスト中、解けませんでした。

問題自体というより、コンテストの時の記憶補正がかかってます。

一見簡単そうに見えるので、解けなかった時のダメージが大きかったです。

そして、このコンテストを機に、問題をきちんと考えてやろうと思い直しました。


それではさらっと、やってきたことのまとめ書いときます。

初期スペック的なもの

  • 大学受験で数学を勉強したので、嫌いではない(※ 得意ではない)
  • 考えるのは好き(※ 得意ではない)
  • 競争はあんまり好きではない (※ 本当かな?)

やってきたこと

その他、お世話になっているもの

  • AtCoder Problems
    • 神サイト。Virtual Contest もできるし、問題の diff も載っているので、精進するときはこのサイト必ず使ってます。

最後に

緑にタッチするまで、遠かったです。

次の目標は安定して、1000パフォーマンスを出すことかなあと思ってます。

無理せず頑張ります。

競プロ楽しい。

isinf を使ってみる (C++)

2020-05-17

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

数値が無限大(infinity)であるか判定する。

test.cpp

#include <bits/stdc++.h>
using namespace std;

int main() {
    double tmp = 1e100000 / 1e-10;

    cout << INFINITY << endl;
    cout << std::numeric_limits<float>::infinity() << endl;
    cout << tmp << endl;

    cout << "-- inf --" << endl;
    cout << isinf(INFINITY) << endl;
    cout << isinf(std::numeric_limits<float>::infinity()) << endl;
    cout << isinf(tmp) << endl;

    cout << "-- not inf --" << endl;
    cout << isinf(0) << endl;
    cout << isinf(100) << endl;
}

出力結果

inf
inf
inf
-- inf --
1
1
1
-- not inf --
0
0

参考

データの一意性を保証するとき、データベース (MySQL) で Unique 制約をつけた方が良さそうな件

2020-04-18

みなさんは、どんなときに MySQL で Unique 制約をつけるでしょうか。

「必要ならつける!」が答えなのですが、どんな時に必要でしょうか。

今回は、「MySQL で Unique 制約をつける場面はどんなときか」というのを考えてみたいと思います。

Unique 制約とは

あるテーブル内のカラムのデータが1つであることを保証します。(一意性)

Web アプリケーションの場合で考えてみる

Web アプリケーション (Rails) でデータの一意性を保証したい場合

  1. Web アプリケーション側 (サーバー側) で、データ保存前に検証する
  2. MySQL 側に Unique 制約をつける

で実現すると思います。

さて最初の問いである「MySQL で Unique 制約をつける場面はどんなときか」の答えとして、

  1. Web アプリケーション側 (サーバー側) で、データ保存前に検証する

さえ満たしていれば、

  1. MySQL 側に Unique 制約をつける

は不要でしょうか。いや必要です。

必要になるケースについて見ていきましょう。

確認環境

$ rails --version
Rails 5.2.3

$ mysql --version
mysql  Ver 14.14 Distrib 5.6.43, for osx10.13 (x86_64) using  EditLine wrapper

検証

準備

MySQL

CREATE TABLE `hoges` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `uniq_test1` int(11) DEFAULT NULL,
  `uniq_test2` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_test2` (`uniq_test2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
INSERT INTO hoges VALUES (null, 11111, 22222, NOW(), NOW());

app/models/hoge.rb

class Hoge < ApplicationRecord
  validates :uniq_test1, uniqueness: true
  validates :uniq_test2, uniqueness: true

  validate :hoge

  def hoge
    sleep(5)
  end
end

Web アプリケーション側 (サーバー側)では、uniq_test1、uniq_test2 ともにデータ保存前に検証します。

今回は、rails console でモデルを create します。

$ rails c
Running via Spring preloader in process 12528
Loading development environment (Rails 5.2.3)
irb(main):002:0> ActiveRecord::Base.transaction do
irb(main):003:1* Hoge.create(uniq_test1: 5, uniq_test2: nil)
irb(main):004:1> end
   (1.9ms)  BEGIN
  Hoge Exists (6.3ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test1` = 5 LIMIT 1
  Hoge Exists (9.9ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test2` IS NULL LIMIT 1
  Hoge Create (4.0ms)  INSERT INTO `hoges` (`uniq_test1`, `created_at`, `updated_at`) VALUES (5, '2020-03-09 16:42:39', '2020-03-09 16:42:39')
   (6.9ms)  COMMIT
=> #<Hoge id: 6, uniq_test1: 5, uniq_test2: nil, created_at: "2020-03-09 16:42:39", updated_at: "2020-03-09 16:42:39">

処理のおおまかな流れはこんな感じです。

  1. MySQL の transaction 開始
  2. データの一意性検証
  3. MySQL の transaction 終了 (ここでデータが作られる)

問題になるのはほぼ同時に処理が実行されたときです。

MySQL に Unique キー制約なし (悪いパターン)

コンソール1

$ rails c
Running via Spring preloader in process 12742
Loading development environment (Rails 5.2.3)
irb(main):001:0> ActiveRecord::Base.transaction do
irb(main):002:1* Hoge.create(uniq_test1: 5, uniq_test2: nil)
irb(main):003:1> end
   (2.2ms)  SET NAMES utf8mb4,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
   (1.1ms)  BEGIN
  Hoge Exists (3.2ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test1` = 5 LIMIT 1
  Hoge Exists (1.4ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test2` IS NULL LIMIT 1
  Hoge Create (4.3ms)  INSERT INTO `hoges` (`uniq_test1`, `created_at`, `updated_at`) VALUES (5, '2020-03-09 16:55:12', '2020-03-09 16:55:12')
   (7.9ms)  COMMIT
=> #<Hoge id: 7, uniq_test1: 5, uniq_test2: nil, created_at: "2020-03-09 16:55:12", updated_at: "2020-03-09 16:55:12">

コンソール2

$ rails c
Running via Spring preloader in process 12757
Loading development environment (Rails 5.2.3)
irb(main):001:0> ActiveRecord::Base.transaction do
irb(main):002:1* Hoge.create(uniq_test1: 5, uniq_test2: nil)
irb(main):003:1> end
   (1.8ms)  SET NAMES utf8mb4,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
   (1.5ms)  BEGIN
  Hoge Exists (2.0ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test1` = 5 LIMIT 1
  Hoge Exists (5.1ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test2` IS NULL LIMIT 1
  Hoge Create (5.1ms)  INSERT INTO `hoges` (`uniq_test1`, `created_at`, `updated_at`) VALUES (5, '2020-03-09 16:55:13', '2020-03-09 16:55:13')
   (49.3ms)  COMMIT
=> #<Hoge id: 8, uniq_test1: 5, uniq_test2: nil, created_at: "2020-03-09 16:55:13", updated_at: "2020-03-09 16:55:13">

Web アプリケーション側 (サーバー側) をすり抜けてしまいました!!!

MySQL に Unique キー制約あり (良いパターン)

コンソール1

$ rails c
Running via Spring preloader in process 12742
Loading development environment (Rails 5.2.3)
irb(main):001:0> ActiveRecord::Base.transaction do
irb(main):002:1* Hoge.create(uniq_test1: nil, uniq_test2: 77777)
irb(main):003:1> end
   (3.7ms)  BEGIN
  Hoge Exists (1.4ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test1` IS NULL LIMIT 1
  Hoge Exists (2.8ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test2` = 77777 LIMIT 1
  Hoge Create (3.5ms)  INSERT INTO `hoges` (`uniq_test2`, `created_at`, `updated_at`) VALUES (77777, '2020-03-09 16:57:01', '2020-03-09 16:57:01')
   (18.2ms)  COMMIT
=> #<Hoge id: 9, uniq_test1: nil, uniq_test2: 77777, created_at: "2020-03-09 16:57:01", updated_at: "2020-03-09 16:57:01">

コンソール2

$ rails c
Running via Spring preloader in process 12757
Loading development environment (Rails 5.2.3)
irb(main):001:0> ActiveRecord::Base.transaction do
irb(main):002:1* Hoge.create(uniq_test1: nil, uniq_test2: 77777)
irb(main):003:1> end
   (7.9ms)  BEGIN
  Hoge Exists (3.7ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test1` IS NULL LIMIT 1
  Hoge Exists (1.8ms)  SELECT  1 AS one FROM `hoges` WHERE `hoges`.`uniq_test2` = 77777 LIMIT 1
  Hoge Create (5.4ms)  INSERT INTO `hoges` (`uniq_test2`, `created_at`, `updated_at`) VALUES (77777, '2020-03-09 16:57:04', '2020-03-09 16:57:04')
   (19.1ms)  ROLLBACK
Traceback (most recent call last):
        2: from (irb):4
        1: from (irb):5:in `block in irb_binding'
ActiveRecord::RecordNotUnique (Mysql2::Error: Duplicate entry '77777' for key 'uniq_test2': INSERT INTO `hoges` (`uniq_test2`, `created_at`, `updated_at`) VALUES (77777, '2020-03-09 16:57:04', '2020-03-09 16:57:04'))

Web アプリケーション側 (サーバー側) をすり抜けたとしても、MySQL に保存したときに MySQL 側のエラーで弾くことができます。

まとめ

データの一意性を保証する必要があるなら、MySQL に Unique キーをつけましょう!

副次的なメリットとして、Web アプリケーション側 (サーバー側) の実装漏れがあった場合も対処できます。