MySQL8 でベースにする文字コードはutf8mb4_unicode_ci

照合順序(COLLATION)とは

照合順序は文字列の比較やソート順のルールのことです。各キャラクタセットごとに照合順序が定義されています。

-- SHOW COLLATIONS で一覧が見れる
mysql> SHOW COLLATIONS;
+----------------------------+----------+-----+---------+----------+---------+---------------+
| Collation                  | Charset  | Id  | Default | Compiled | Sortlen | Pad_attribute |
+----------------------------+----------+-----+---------+----------+---------+---------------+
| armscii8_bin               | armscii8 |  64 |         | Yes      |       1 | PAD SPACE     |
| armscii8_general_ci        | armscii8 |  32 | Yes     | Yes      |       1 | PAD SPACE     |
| ascii_bin                  | ascii    |  65 |         | Yes      |       1 | PAD SPACE     |
| ascii_general_ci           | ascii    |  11 | Yes     | Yes      |       1 | PAD SPACE     |
| big5_bin                   | big5     |  84 |         | Yes      |       1 | PAD SPACE     |
| big5_chinese_ci            | big5     |   1 | Yes     | Yes      |       1 | PAD SPACE     |
| binary                     | binary   |  63 | Yes     | Yes      |       1 | NO PAD        |
<snip>

MySQL 8.0 で、utf8mb4 の照合順序が増えました。以下の表で太字にしたものが、新規に追加されたものです。各文字列が、同一と扱われる場合は、○としています。
ci/csCase [In]sensitive の略で、asAcent SensiteveksKatakana Sensitive の略です。

COLLATION A、a はは、ぱぱ はは、ハハ びょういん、びよういん 🍣、🍺 +、+
utf8mb4_bin × × × × × ×
utf8mb4_0900_bin × × × × × ×
utf8mb4_unicode_ci
utf8mb4_general_ci × × × ×
utf8mb4_unicode_520_ci ×
utf8mb4_0900_ai_ci ×
utf8mb4_0900_as_ci × ×
utf8mb4_ja_0900_as_cs × × × ×
utf8mb4_ja_0900_as_cs_ks × × × × ×

アルファベットの大文字・小文字を区別しない要件で、どれが選ばれそうか・・・
utf8mb4_0900_as_ci は「びょういん」「びよういん」が同一と扱われてしまい、いまいちに感じます。

そもそも、日本語の文字列比較やソート結果を網羅的に精査するのは現実的に可能なんでしょうか(上記の表以外にも考えないといけない、パターンがありそうです)。日本語には異字体・長音記号・漢数字・・・ちょっと思いつくだけでも、扱いに悩みそうな要素が多くあります。

絵文字が区別できないとは言え、utf8mb4_general_ci にはずっと使ってきた実績と安心があります。
MySQL 8.0 でも utf8mb4_general_ci を 引き続き使うケースが多いのではないでしょうか。

MySQL 8.0 で utf8mb4_general_ci を使うときの注意点

ALTER TABLE CONVERT TO 時に COLLATION の指定が必要

MySQL 8.0 で utf8mb4 のデフォルトの照合順序が utf8mb4_general_ci から utf8mb4_0900_as_ci に変更になりました。

あわせて、従来の3バイトUTF8、utf8(mb3) は deprecated になっています。
utf8mb4 に変換するときに COLLATE を明示的に指定しないと、utf8_general_ci から utf8mb4_0900_ai_ci へとテーブルのデフォルト照合順序になってしまいます。

mysql> SELECT * FROM utf8t WHERE c1 = "ぱぱ";
Empty set (0.00 sec)

mysql> ALTER TABLE utf8t CONVERT TO CHARACTER SET 'utf8mb4';
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

-- 「ぱぱ」で「はは」がヒットしてしまう
mysql> SELECT * FROM utf8t WHERE c1 = "ぱぱ";
+----+--------+
| pk | c1     |
+----+--------+
|  1 | はは   |
+----+--------+
1 row in set (0.00 sec)

mysql> SHOW CREATE TABLE utf8t \G
*************************** 1. row ***************************
       Table: utf8t
Create Table: CREATE TABLE `utf8t` (
  `pk` bigint unsigned NOT NULL AUTO_INCREMENT,
  `c1` varchar(255) DEFAULT NULL,
  UNIQUE KEY `pk` (`pk`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

このように、COLLATE を指定してALTERする必要があります。

mysql> ALTER TABLE utf8t CONVERT TO CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql>  SHOW CREATE TABLE utf8t \G
*************************** 1. row ***************************
       Table: utf8t
Create Table: CREATE TABLE `utf8t` (
  `pk` bigint unsigned NOT NULL AUTO_INCREMENT,
  `c1` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
  UNIQUE KEY `pk` (`pk`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
1 row in set (0.00 sec)

SET NAMES では COLLATE の指定が必要

同様に、SET NAMES で照合順序を明示的に指定していない場合、MySQL 8.0 からは utf8mb4_0900_as_ci が使われてしまいます。

# MySQL 8.0 以降は utf8mb4_0900_as_ci が使われる
mysql> SET NAMES utf8mb4;

# MySQL 8.0 以降は 明示的に utf8mb4_general_ci を指定する必要がある。
mysql> SET NAMES utf8mb4 COLLATE utf8mb4_general_ci;

Unknown collation: ‘utf8mb4_unicode_520_ci’

原因は移行先サーバーのMySQLがMySQL5.5以下であるためです。例えば某Sサーバーなど..。utf8mb4_unicode_520_ciというcollationはMySQL5.6以上でしか利用できません。
現行のWordPressはutf8mb4_unicode_520_ciが使用できるサーバーではutf8mb4_unicode_520_ciを優先的に使用してインストールを行います。charsetとcollationを決定するwp-db.phpのコードは以下のようになっています。

	/**
	 * Determines the best charset and collation to use given a charset and collation.
	 *
	 * For example, when able, utf8mb4 should be used instead of utf8.
	 *
	 * @since 4.6.0
	 * @access public
	 *
	 * @param string $charset The character set to check.
	 * @param string $collate The collation to check.
	 * @return array The most appropriate character set and collation to use.
	 */
	public function determine_charset( $charset, $collate ) {
		if ( ( $this->use_mysqli && ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) {
			return compact( 'charset', 'collate' );
		}

		if ( 'utf8' === $charset && $this->has_cap( 'utf8mb4' ) ) {
			$charset = 'utf8mb4';
		}

		if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) {
			$charset = 'utf8';
			$collate = str_replace( 'utf8mb4_', 'utf8_', $collate );
		}

		if ( 'utf8mb4' === $charset ) {
			// _general_ is outdated, so we can upgrade it to _unicode_, instead.
			if ( ! $collate || 'utf8_general_ci' === $collate ) {
				$collate = 'utf8mb4_unicode_ci';
			} else {
				$collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
			}
		}

		// _unicode_520_ is a better collation, we should use that when it's available.
		if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
			$collate = 'utf8mb4_unicode_520_ci';
		}

		return compact( 'charset', 'collate' );
	}

 

対策・解決方法

インポートするSQLファイルをエディタで開き、以下のように置き換えます。

utf8mb4_unicode_520_ci → utf8_general_ci

utf8mb4 → utf8

[参考]

https://stackoverflow.com/questions/29916610/1273-unknown-collation-utf8mb4-unicode-ci-cpanel/29939906#29939906

 

2018年以降のサーバーで、EC-CUBE2系を動かすには?

2016〜2018年頃、レンタルサーバーでPHPやSQLのバージョンアップにより
いろんなオープンソースが対応できなくなるケースが多発しています。

EC-CUBE2系の場合、症状は様々ですが
・サイトが表示されない
・インストールが完了できない
・その他

原因の一つとして
SET SESSION storage_engine = InnoDB の実行時に以下のエラーで実行できないケースがあります。

/data/class/db/dbfactory/SC_DB_DBFactory_MYSQL.php の 末尾付近を編集します。

    function initObjQuery(SC_Query &$objQuery) {
        // $objQuery->exec('SET SESSION storage_engine = InnoDB');
	$objQuery->exec('SET SESSION default_storage_engine = InnoDB');
        $objQuery->exec("SET SESSION sql_mode = 'ANSI'");
    }

MySQL カラムのコメントを取得する

SQL文の半自動化に、さらなる進化がありました。

MySQLのカラムコメントを抽出することで
フォームの半自動化→7割自動化 を実現しました。

//構文設定
$stmt = $pdo->prepare('SHOW FULL columns FROM ' . wr_user);

//実行
$stmt->execute();

$array_comment = array();
$i = 0;
foreach($stmt->fetchAll() as $result):
	array_push($array_comment, $result['Comment']);
	$i ++;
endforeach;

var_dump($array_comment);

SQL文 を半自動で生成する


$statement = $pdo->query($sql);
$item = $statement->fetch(PDO::FETCH_ASSOC);
$i = 0;
foreach($item as $key => $val){
    if($i == 0){
        $kanma = '';
    }else{
        $kanma = ',';
    }
    $key_text .= $kanma."`".$key."`";
    $val_text .= $kanma."'".$_POST[$key]."'";
    $i ++;
}

echo '<hr>'.$key_text.'<hr>';
echo $val_text.'<hr>';

$sql = "INSERT INTO `tb_staff` ($key_text) VALUES($val_text)";
echo $sql;

$statement = $pdo->query($sql);
$pdo = new PDO('mysql:host = '.DB_SERVER.';dbname='.DB_NAME.';charset=utf8', DB_USER, DB_PASSWORD);
$sql = "SELECT * FROM `wr_user` LIMIT 1";
$statement = $pdo->query($sql);
$item = $statement->fetch(PDO::FETCH_ASSOC);

$i = 0;
foreach($item as $key => $val){

	if($i == 0){
		$kanma = '';
	}else{
		$kanma = ',';
	}

	$key_text .= $kanma."`".$key."`";
	$val_text .= $kanma."'$".$key."'";

	$i ++;
}
echo '<hr>'.$key_text.'<hr>';
echo $val_text.'<hr>';


$sql = "INSERT INTO `wr_user` ($key_text) VALUES($val_text)";
echo $sql;
$db = mysql_connect(DB_HOST.":".DB_PORT,DB_USER,DB_PASSWORD);
// mysql_query("SET NAMES utf8");
mysql_set_charset('utf8');
mysql_select_db(DB_NAME, $db);


$sql = "SELECT * FROM wr_posts LIMIT 1";
$rs = mysql_query($sql,$db);
$item = mysql_fetch_assoc($rs);
var_dump($item);


$i = 0;
foreach($item as $key => $val){

	if($i == 0){
		$kanma = '';
	}else{
		$kanma = ',';
	}

	$key_text .= $kanma."`".$key."`";
	$val_text .= $kanma."'$".$key."'";

	$i ++;
}
echo '<hr>'.$key_text.'<hr>';
echo $val_text.'<hr>';

自動生成の例
INSERT INTO `wr_user`
  (`user_id`,`f1`,`f2`,`f3`)   
  VALUES
  (‘exampleuser_id’,’examplef1′,’examplef2′,’examplef3′)

MySQLのフィールド名をSQL文用に自動で整形する。

MySQLのフィールド名(カラム名)をSQL文用に自動で整形する。

$sql = "SELECT * FROM `dtb_customer` LIMIT 1";
$rs = mysql_query($sql,$db);
$item = mysql_fetch_assoc($rs);
// Key を配列化
$key_arr = array();
foreach($item as $key => $val){
	echo $key."<br>";
}
$sql = "SELECT * FROM `dtb_products` LIMIT 1";
$rs = mysql_query($sql,$db);
$item = mysql_fetch_assoc($rs);
// Key を配列化
$key_arr = array();
foreach($item as $key => $val){
	//      echo $key."=>".$val."<br>"; // 試験表示
	//     array_push($key_arr, $key);
	echo "`".$key."`,";
}

SQLデータベースに接続の基本コード

php5〜php7

Database に接続

$pdo = new PDO('mysql:host='.DB_SERVER.';dbname='.DB_NAME.';charset=utf8', DB_USER, DB_PASSWORD);

ヒットがユニークの場合

$sql = "SELECT * FROM `tb_hogehoge`";
$statement = $pdo->query($sql);
$item = $statement->fetch(PDO::FETCH_ASSOC);
var_dump($item);

Roop の場合

$sql = "SELECT * FROM `tb_hogehoge`";
foreach($pdo->query($sql) as $item) {
	var_dump($item);
}

↓php7 から使えなくなる。

$db = mysql_connect(DB_SERVER.":".DB_PORT,DB_USER,DB_PASSWORD);
// mysql_query("SET NAMES utf8");
mysql_set_charset('utf8');
mysql_select_db(DB_NAME, $db);
$sql1 = "SELECT * FROM `dtb_category` WHERE `category_id` = '$_GET[category_id]'";
$rs1 = mysql_query($sql1,$db);
$item1 = mysql_fetch_assoc($rs1);

MySQL 複数テーブルを結合して検索する

複数のテーブルを結合して、一つのテーブルのように見立てて、WHEREできる。

tb_menber
menber_id name birthday address

tb_profile
tall blood hobby

SELECT * FROM `tb_member` INNER JOIN `tb_profile`

すると
menber_id name birthday address tall blood hobby
に対して WHERE文が使える

AUTO_INCREMENTの値を更新する

AUTO_INCREMENTの値として代入する数値は「’」コロンで囲まない。

$sql = "ALTER TABLE `dtb_products_class_product_class_id_seq` AUTO_INCREMENT = 777";

ついでに
EC-CUBE のあるバージョンで発生した事例。

CSVによる商品登録のさい
dtb_products_class_product_class_id_seq や dtb_products_product_id_seq において
・AUTO_INCREMENT を更新する
・sequence を更新する
両方更新しないと管理画面からの商品登録でエラーになる。

phpMyadminにログインできない

 

ユーザー名やパスワードが間違っていない。
config.inc.php が間違っていない。
 

ログインがエラーにならないのに
ログイン画面に戻ってしまう。
ブラウザによっては「アクセスが拒否されました」と表示される。
 

つまり、パスワード云々ではなく、拒否される
権限が無いのね。
 

試しに
phpMyadmin/setup にアクセスしてみる。
↓こんなエラーだ
session_start(*************): open failed: Permission denied (13)
セッションが書き込めないのね、ということで
 

/var/lib/php/session のパーミッションを確認する

[root@hogehoge php]# ls -l
drwxrwx— 2 apache apache session

 

apache からの書き込みがOKでも動かない
動いているサーバーの設定を見てみると
webユーザーにも許可が出ている。
 

webユーザーも読み書きできるようにパーミッションを変えたら
phpMyadmin が無事に動きました。

EC-CUBEやWordpressは動いていたので、session_save_path を疑いませんでしたが
彼らは session_save_path を指定して権限を付与している。
phpMyadmin はデフォルトのsession_save_path を使っている。
session_save_path を指定して、権限を付与するのもOKってことですね。

データベースのメンテナンス

データベースのオーバーヘッドは、INSERT、DELETE、UPDATEを行っているうちにできるゴミ(未使用)領域のようなものです。

これを解消するためには、テーブルの最適化が必要です。

phpMyAdminからDB表示、画面一番下の「オーバーヘッドのあるテーブルを確認」→チェックが入る→ドロップダウンリストから「テーブルを最適化する」、で実行できます。

MySQLの条件ソートで大文字小文字が区別されない

WHEREで大文字小文字が区別されないなんて迷惑な機能があります。
大文字小文字を区別するには条件式の前に”INARY”を付加するといいらしい。

SELECT * FROM `dtb_coupon` WHERE BINARY `coupon_code` = ‘$_POST[use_coupon]’ AND `del_flg` = ‘0’

AUTO_INCREMENTの値をリセットする方法

EC-CUBEなどで、カテゴリIDや商品IDを指定したいということもあるようです。
AUTO_INCREMENT(自動採番)を採用しているのは、指定する必要性が考えられないからです。
番号を指定したいというご要望も多いですね。
理由は聞かないことにしています。
辻褄の合う理由など存在しないのですから、聞いて腹だ立つのなら、聞かずに対応してしまいましょう。

操作は至って簡単!

ALTER TABLE &lt;テーブル名&gt; AUTO_INCREMENT = 1;

関連テーブルのIDの変更もお忘れなく…

フィールド名とフォーム名の自動化

フィールド名を勝手に抽出し、
POSTがあれば、POSTを優先
無ければDBを優先
これを勝手に行うソースコード

この記事に出会ったあなたはラッキーです。

// テーブル抽出(1レコードのみ)
$sql = "SELECT * FROM `TABLE_NAME` LIMIT 1";
$statement = $pdo->query($sql);
$item = $statement->fetch(PDO::FETCH_ASSOC);

// Key を配列化
$key_arr = array();
foreach($item as $key => $val){
    // echo $key."=>".$val."&lt;br&gt;"; // 試験表示
    array_push($key_arr, $key);
}

// POST があれば変数に代入、無ければテーブルから変数に代入
$i="0";
while($key_arr[$i]!=""){
    $value=$key_arr[$i];
    
    // POST があれば変数に代入
    if(($_POST[$value]!="") && ($_POST[$value]!="NULL")){
        $$value=$_POST[$value];
    
    // 無ければテーブルから変数に代入
    }else{
        $$value=$item[$value];
    }
    
    $i ++;
}

Perl で Mysql

ひとつはPPMにCPANを登録する事。
メニューの「Edit」→「Reference」→「Repositories」にて「Name」は適当に、「Location」に「http://cpan.uwinnipeg.ca/PPMPackages/10xx/」と入力して「Add」ボタンをクリックして登録。
これでCPANに接続しする事ができ、CPANからもモジュールをダウンロードする事ができる。

ふたつ目はコマンドプロンプトから「perl -MCPAN -e shell」と入力。
これはCPANとコマンドで対話する事ができるシェルコマンド。
「cpan>」の後に「install DBD::mysql」と入力してモジュールをダウンロード&インストールするのだ。

レコードの検索

////////////////
// 比較演算子 //
////////////////

where フィールド名 = ‘値’ レコードが値と等しい場合
where フィールド名 <> ‘値’ レコードが値と等しくない場合
where フィールド名 > ‘値’ レコードが値よりも大きい場合
where フィールド名 >= ‘値’ レコードが値以上である場合
where フィールド名 < '値' レコードが値より小さい場合 where フィールド名 <= '値' レコードが値以下である場合 //////////////////// // その他の演算子 // //////////////////// where フィールド名 between '値1' and '値2' レコードが値1と値2の間である場合値1と値2も含む where フィールド名 in ('値1', '値2'...) レコードがいずれかの値である場合複数の値を指定できる where フィールド名 is null レコードがnull値である場合 where フィールド名 is not null レコードがnull値でない場合 where フィールド名 like '値%' 値から始まるレコードを検索前方一致 where フィールド名 like '%値%' 値を含むレコードを検索中間一致 where フィールド名 like '%値' 値で終わるレコードを検索後方一致 //////////////// // 論理演算子 // //////////////// where 条件1 and 条件2 条件1でありかつまた条件2である場合(論理積) where 条件1 or 条件2 条件1あるいは条件2である場合(論理和) where not (条件) 条件でない場合(論理否定)

郵便番号を住所変換する

DBは郵便サイトからダウンロード
http://www.post.japanpost.jp/zipcode/dl/kogaki.html

SQL文(1)
テーブル生成

CREATE TABLE mtb_zipcode (
jis varchar(10) NULL,
zip_old varchar(5) NULL,
zip varchar(7) NULL,
addr1_kana varchar(100) NULL,
addr2_kana varchar(100) NULL,
addr3_kana varchar(100) NULL,
addr1 varchar(100) NULL,
addr2 varchar(100) NULL,
addr3 varchar(100) NULL,
c1 int NULL,
c2 int NULL,
c3 int NULL,
c4 int NULL,
c5 int NULL,
c6 int NULL
)

SQL文(2)
データ挿入
vscファイルのエンコードを調整しておく

LOAD DATA LOCAL INFILE '/home/_DATABASE_BACKUP/KEN_ALL_UTF8.csv'
INTO TABLE `tb_zip_code`
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"'