sql >> データベース >  >> RDS >> Mysql

MySQLの管理方法-OracleDBAの場合

    オープンソースデータベースは急速に主流になりつつあるため、プロプライエタリエンジンからオープンソースエンジンへの移行は、現在、一種の業界トレンドです。また、DBAが管理するデータベースバックエンドを複数持つことになることもよくあります。

    過去数回のブログ投稿で、同僚のPaul Namuagと私は、OracleからPercona、MariaDB、およびMySQLへの移行のいくつかの側面について説明しました。移行の明らかな目標は、新しいデータベース環境でアプリケーションをより効率的に稼働させることですが、スタッフがそれをサポートする準備ができていることを確認することが重要です。

    このブログでは、Oracle環境で毎日実行する同様のタスクを参照して、MySQLの基本的な操作について説明します。さまざまなトピックについて深く掘り下げて、長年にわたって構築したOracleの知識に関連する時間を節約できます。

    また、デフォルトのMySQLインストールにはないが、日常の操作を効率的に実行するために必要な外部コマンドラインツールについても説明します。たとえば、オープンソースバージョンにはOracle Cloud Controlに相当するものが付属していないため、同様のものを探している場合はClusterControlをチェックアウトしてください。

    このブログでは、MySQLよりもOracleの知識が豊富であると想定しているため、2つの間の相関関係を知りたいと考えています。例はLinuxプラットフォームに基づいていますが、WindowsでのMySQLの管理には多くの類似点があります。

    MySQLに接続するにはどうすればよいですか?

    非常に(一見)基本的なタスクから旅を始めましょう。実際、これは一種のタスクであり、OracleとMySQLのログイン概念が異なるために混乱を招く可能性があります。

    sysdba接続としてのsqlplus/に相当するのは、フラグ-urootを持つ「mysql」ターミナルコマンドです。 MySQLの世界では、スーパーユーザーはrootと呼ばれます。 MySQLデータベースユーザー(rootを含む)は、接続できる名前とホストによって定義されます。

    接続できるユーザーとホストに関する情報は、mysql.userテーブルに格納されます。接続を試みると、MySQLは、クライアントホスト、ユーザー名、およびパスワードがメタデータテーブルの行と一致するかどうかを確認します。

    これは、ユーザー名とパスワードのみを使用するOracleとは少し異なるアプローチですが、OracleConnectionManagerに精通している人はいくつかの類似点を見つける可能性があります。

    Oracleのように事前定義されたTNSエントリはありません。通常、管理者接続には、ユーザー、パスワード、および-hホストフラグが必要です。デフォルトのポートは3306(Oracleの1521など)ですが、これは設定によって異なる場合があります。

    デフォルトでは、多くのインストールで任意のマシン([email protected]’%’)からのルートアクセス接続がブロックされるため、通常はsshを介してMySQLをホストしているサーバーにログインする必要があります。

    次のように入力します:

    mysql -u root

    ルートパスワードが設定されていない場合は、これで十分です。パスワードが必要な場合は、フラグ-pを追加する必要があります。

    mysql -u root -p

    これで、mysqlクライアント(sqlplusと同等)にログインし、プロンプト(通常は「mysql>」)が表示されます。

    MySQLは稼働していますか?

    mysqlサービス起動スクリプトまたはmysqladminコマンドを使用して、実行されているかどうかを確認できます。次に、psコマンドを使用して、mysqlプロセスが稼働しているかどうかを確認できます。別の代替手段として、管理操作を実行するために使用されるユーティリティであるmysqladminがあります。

    mysqladmin -u root -p status

    Debianの場合:

    /etc/init.d/mysql status

    RedHatまたはFedoraを使用している場合は、次のスクリプトを使用できます。

    service mysqld status

    または

    /etc/init.d/mysqld status

    または

    systemctl status mysql.service

    MariaDBインスタンスでは、MariaDBサービス名を探す必要があります。

    systemctl status mariadb

    このデータベースには何が含まれていますか?

    Oracleと同様に、メタデータオブジェクトをクエリして、データベースオブジェクトに関する情報を取得できます。

    ここでは、オブジェクトの一覧表示やオブジェクトのDDLの取得に役立つコマンドなどのショートカットを使用するのが一般的です。

    show databases;
    use database_name;
    show tables;
    show table status;
    show index from table_name;
    show create table table_name;

    Oracleと同様に、テーブルを記述できます。

    desc table_name;

    データはどこに保存されますか?

    MySQLにはASMのような専用の内部ストレージはありません。すべてのデータファイルは、通常のOSマウントポイントに配置されます。デフォルトのインストールでは、次の場所にデータを見つけることができます:

    /var/lib/mysql

    場所は変数datadirに基づいています。

    [email protected]:~# cat /etc/mysql/my.cnf | grep datadir
    datadir=/var/lib/mysql

    各データベースのディレクトリが表示されます。

    バージョンとストレージエンジンによっては(ここにいくつかあります)、データベースのディレクトリには、データベース内の各テーブルの構造を定義する*.frm形式のファイルが含まれている場合があります。 MyISAMテーブルの場合、データ(* .MYD)とインデックス(* .MYI)もこのディレクトリに保存されます。

    InnoDBテーブルは、InnoDBテーブルスペースに格納されます。それぞれが、Oracleテーブルスペースに類似した1つ以上のファイルで構成されています。デフォルトのインストールでは、MySQLサーバー上のすべてのデータベースのすべてのInnoDBデータとインデックスは、1つのファイル/ var / lib / mysql/ibdata1で構成される1つのテーブルスペースに保持されます。ほとんどのセットアップでは、Oracleのようにテーブルスペースを管理しません。ベストプラクティスは、自動拡張をオンにして最大サイズを無制限にしておくことです。

    [email protected]:~# cat /etc/mysql/my.cnf | grep innodb-data-file-path
    innodb-data-file-path = ibdata1:100M:autoextend

    InnoDBには、Oracle REDOログと同等のログファイルがあり、自動クラッシュリカバリが可能です。デフォルトでは、/ var / lib / mysql/ib_logfile0と/var/ lib / mysql/ib_logfile1の2つのログファイルがあります。元に戻すデータはテーブルスペースファイル内に保持されます。

    [email protected]:/var/lib/mysql# ls -rtla | grep logfile
    -rw-rw----  1 mysql mysql  268435456 Dec 15 00:59 ib_logfile1
    -rw-rw----  1 mysql mysql  268435456 Mar  6 11:45 ib_logfile0

    メタデータ情報はどこにありますか?

    dba _ *、user _ *、all_ *タイプのビューはありませんが、MySQLには内部メタデータビューがあります。

    Information_schemaはSQL2003標準で定義されており、他の主要なデータベースによって実装されています。 SQL Server、PostgreSQL。

    MySQL 5.0以降、データディクショナリ情報を含むinformation_schemaデータベースが利用可能になりました。情報は実際には外部FRMファイルに保存されていました。最後に、何年も経った後、.frmファイルはバージョン8.0でなくなりました。メタデータは引き続きinformation_schemaデータベースに表示されますが、InnoDBストレージエンジンを使用します。

    mysqlクライアント内のデータディクショナリに含まれるすべての実際のビューを表示するには、information_schemaデータベースに切り替えます。

    use information_schema;
    show tables;

    MySQLデータベースには、db、イベント(MySQLジョブ)、プラグイン、レプリケーション、データベース、ユーザーなどに関する情報が含まれている追加情報があります。

    ビューの数は、バージョンとベンダーによって異なります。

    v$sessionから*を選択

    Oracleのselect*from v $ sessionは、スレッドのリストを表示するコマンドSHOWPROCESSLISTで表されます。

    mysql> SHOW PROCESSLIST;
    +---------+------------------+------------------+--------------------+---------+--------+--------------------+------------------+-----------+---------------+
    | Id      | User             | Host             | db                 | Command | Time   | State              | Info             | Rows_sent | Rows_examined |
    +---------+------------------+------------------+--------------------+---------+--------+--------------------+------------------+-----------+---------------+
    |       1 | system user      |                  | NULL               | Sleep   | 469264 | wsrep aborter idle | NULL             |         0 |             0 |
    |       2 | system user      |                  | NULL               | Sleep   | 469264 | NULL               | NULL             |         0 |             0 |
    |       3 | system user      |                  | NULL               | Sleep   | 469257 | NULL               | NULL             |         0 |             0 |
    |       4 | system user      |                  | NULL               | Sleep   | 469257 | NULL               | NULL             |         0 |             0 |
    |       6 | system user      |                  | NULL               | Sleep   | 469257 | NULL               | NULL             |         0 |             0 |
    |      16 | maxscale         | 10.0.3.168:5914  | NULL               | Sleep   |      5 |                    | NULL             |         4 |             4 |
    |      59 | proxysql-monitor | 10.0.3.168:6650  | NULL               | Sleep   |      7 |                    | NULL             |         0 |             0 |
    |      81 | proxysql-monitor | 10.0.3.78:62896  | NULL               | Sleep   |      6 |                    | NULL             |         0 |             0 |
    |    1564 | proxysql-monitor | 10.0.3.78:25064  | NULL               | Sleep   |      3 |                    | NULL             |         0 |             0 |
    | 1822418 | cmon             | 10.0.3.168:41202 | information_schema | Sleep   |      0 |                    | NULL             |         0 |             8 |
    | 1822631 | cmon             | 10.0.3.168:43254 | information_schema | Sleep   |      4 |                    | NULL             |         1 |             1 |
    | 1822646 | cmon             | 10.0.3.168:43408 | information_schema | Sleep   |      0 |                    | NULL             |       464 |           464 |
    | 2773260 | backupuser       | localhost        | mysql              | Query   |      0 | init               | SHOW PROCESSLIST |         0 |             0 |
    +---------+------------------+------------------+--------------------+---------+--------+--------------------+------------------+-----------+---------------+
    
    
    13 rows in set (0.00 sec)

    これは、information_schema.processlistビューに格納されている情報に基づいています。ビューには、PROCESS特権が必要です。また、最大数のプロセスが不足していないかどうかを確認するのにも役立ちます。

    アラートログはどこにありますか?

    エラーログは、my.cnfまたはshowvariablesコマンドで確認できます。

    mysql> show variables like 'log_error';
    +---------------+--------------------------+
    | Variable_name | Value                    |
    +---------------+--------------------------+
    | log_error     | /var/lib/mysql/error.log |
    +---------------+--------------------------+
    1 row in set (0.00 sec)

    ユーザーとその権限のリストはどこにありますか?

    ユーザーに関する情報はmysql.userテーブルに保存され、助成金はmysql.user、mysql.tables_priv、

    などのいくつかの場所に保存されます。

    MySQLユーザーアクセスは次のように定義されています:

    mysql.columns_priv, mysql.tables_priv, mysql.db,mysql.user

    助成金を一覧表示するための好ましい方法は、Perconaツールキットのツールであるpt-grantsを使用することです(すべてのMySQL DBAに必須です)。

    pt-show-grants --host localhost --user root --ask-pass

    または、次のクエリ(Calvaldoによって作成)を使用することもできます

    SELECT
        CONCAT("`",gcl.Db,"`") AS 'Database(s) Affected',
        CONCAT("`",gcl.Table_name,"`") AS 'Table(s) Affected',
        gcl.User AS 'User-Account(s) Affected',
        IF(gcl.Host='%','ALL',gcl.Host) AS 'Remote-IP(s) Affected',
        CONCAT("GRANT ",UPPER(gcl.Column_priv)," (",GROUP_CONCAT(gcl.Column_name),") ",
                     "ON `",gcl.Db,"`.`",gcl.Table_name,"` ",
                     "TO '",gcl.User,"'@'",gcl.Host,"';") AS 'GRANT Statement (Reconstructed)'
    FROM mysql.columns_priv gcl
    GROUP BY CONCAT(gcl.Db,gcl.Table_name,gcl.User,gcl.Host)
    /* SELECT * FROM mysql.columns_priv */
    
    UNION
    
    /* [Database.Table]-Specific Grants */
    SELECT
        CONCAT("`",gtb.Db,"`") AS 'Database(s) Affected',
        CONCAT("`",gtb.Table_name,"`") AS 'Table(s) Affected',
        gtb.User AS 'User-Account(s) Affected',
        IF(gtb.Host='%','ALL',gtb.Host) AS 'Remote-IP(s) Affected',
        CONCAT(
            "GRANT ",UPPER(gtb.Table_priv)," ",
            "ON `",gtb.Db,"`.`",gtb.Table_name,"` ",
            "TO '",gtb.User,"'@'",gtb.Host,"';"
        ) AS 'GRANT Statement (Reconstructed)'
    FROM mysql.tables_priv gtb
    WHERE gtb.Table_priv!=''
    /* SELECT * FROM mysql.tables_priv */
    
    UNION
    
    /* Database-Specific Grants */
    SELECT
        CONCAT("`",gdb.Db,"`") AS 'Database(s) Affected',
        "ALL" AS 'Table(s) Affected',
        gdb.User AS 'User-Account(s) Affected',
        IF(gdb.Host='%','ALL',gdb.Host) AS 'Remote-IP(s) Affected',
        CONCAT(
            'GRANT ',
            CONCAT_WS(',',
                IF(gdb.Select_priv='Y','SELECT',NULL),
                IF(gdb.Insert_priv='Y','INSERT',NULL),
                IF(gdb.Update_priv='Y','UPDATE',NULL),
                IF(gdb.Delete_priv='Y','DELETE',NULL),
                IF(gdb.Create_priv='Y','CREATE',NULL),
                IF(gdb.Drop_priv='Y','DROP',NULL),
                IF(gdb.Grant_priv='Y','GRANT',NULL),
                IF(gdb.References_priv='Y','REFERENCES',NULL),
                IF(gdb.Index_priv='Y','INDEX',NULL),
                IF(gdb.Alter_priv='Y','ALTER',NULL),
                IF(gdb.Create_tmp_table_priv='Y','CREATE TEMPORARY TABLES',NULL),
                IF(gdb.Lock_tables_priv='Y','LOCK TABLES',NULL),
                IF(gdb.Create_view_priv='Y','CREATE VIEW',NULL),
                IF(gdb.Show_view_priv='Y','SHOW VIEW',NULL),
                IF(gdb.Create_routine_priv='Y','CREATE ROUTINE',NULL),
                IF(gdb.Alter_routine_priv='Y','ALTER ROUTINE',NULL),
                IF(gdb.Execute_priv='Y','EXECUTE',NULL),
                IF(gdb.Event_priv='Y','EVENT',NULL),
                IF(gdb.Trigger_priv='Y','TRIGGER',NULL)
            ),
            " ON `",gdb.Db,"`.* TO '",gdb.User,"'@'",gdb.Host,"';"
        ) AS 'GRANT Statement (Reconstructed)'
    FROM mysql.db gdb
    WHERE gdb.Db != ''
    /* SELECT * FROM mysql.db */
    
    UNION
    
    /* User-Specific Grants */
    SELECT
        "ALL" AS 'Database(s) Affected',
        "ALL" AS 'Table(s) Affected',
        gus.User AS 'User-Account(s) Affected',
        IF(gus.Host='%','ALL',gus.Host) AS 'Remote-IP(s) Affected',
        CONCAT(
            "GRANT ",
            IF((gus.Select_priv='N')&(gus.Insert_priv='N')&(gus.Update_priv='N')&(gus.Delete_priv='N')&(gus.Create_priv='N')&(gus.Drop_priv='N')&(gus.Reload_priv='N')&(gus.Shutdown_priv='N')&(gus.Process_priv='N')&(gus.File_priv='N')&(gus.References_priv='N')&(gus.Index_priv='N')&(gus.Alter_priv='N')&(gus.Show_db_priv='N')&(gus.Super_priv='N')&(gus.Create_tmp_table_priv='N')&(gus.Lock_tables_priv='N')&(gus.Execute_priv='N')&(gus.Repl_slave_priv='N')&(gus.Repl_client_priv='N')&(gus.Create_view_priv='N')&(gus.Show_view_priv='N')&(gus.Create_routine_priv='N')&(gus.Alter_routine_priv='N')&(gus.Create_user_priv='N')&(gus.Event_priv='N')&(gus.Trigger_priv='N')&(gus.Create_tablespace_priv='N')&(gus.Grant_priv='N'),
                "USAGE",
                IF((gus.Select_priv='Y')&(gus.Insert_priv='Y')&(gus.Update_priv='Y')&(gus.Delete_priv='Y')&(gus.Create_priv='Y')&(gus.Drop_priv='Y')&(gus.Reload_priv='Y')&(gus.Shutdown_priv='Y')&(gus.Process_priv='Y')&(gus.File_priv='Y')&(gus.References_priv='Y')&(gus.Index_priv='Y')&(gus.Alter_priv='Y')&(gus.Show_db_priv='Y')&(gus.Super_priv='Y')&(gus.Create_tmp_table_priv='Y')&(gus.Lock_tables_priv='Y')&(gus.Execute_priv='Y')&(gus.Repl_slave_priv='Y')&(gus.Repl_client_priv='Y')&(gus.Create_view_priv='Y')&(gus.Show_view_priv='Y')&(gus.Create_routine_priv='Y')&(gus.Alter_routine_priv='Y')&(gus.Create_user_priv='Y')&(gus.Event_priv='Y')&(gus.Trigger_priv='Y')&(gus.Create_tablespace_priv='Y')&(gus.Grant_priv='Y'),
                    "ALL PRIVILEGES",
                    CONCAT_WS(',',
                        IF(gus.Select_priv='Y','SELECT',NULL),
                        IF(gus.Insert_priv='Y','INSERT',NULL),
                        IF(gus.Update_priv='Y','UPDATE',NULL),
                        IF(gus.Delete_priv='Y','DELETE',NULL),
                        IF(gus.Create_priv='Y','CREATE',NULL),
                        IF(gus.Drop_priv='Y','DROP',NULL),
                        IF(gus.Reload_priv='Y','RELOAD',NULL),
                        IF(gus.Shutdown_priv='Y','SHUTDOWN',NULL),
                        IF(gus.Process_priv='Y','PROCESS',NULL),
                        IF(gus.File_priv='Y','FILE',NULL),
                        IF(gus.References_priv='Y','REFERENCES',NULL),
                        IF(gus.Index_priv='Y','INDEX',NULL),
                        IF(gus.Alter_priv='Y','ALTER',NULL),
                        IF(gus.Show_db_priv='Y','SHOW DATABASES',NULL),
                        IF(gus.Super_priv='Y','SUPER',NULL),
                        IF(gus.Create_tmp_table_priv='Y','CREATE TEMPORARY TABLES',NULL),
                        IF(gus.Lock_tables_priv='Y','LOCK TABLES',NULL),
                        IF(gus.Execute_priv='Y','EXECUTE',NULL),
                        IF(gus.Repl_slave_priv='Y','REPLICATION SLAVE',NULL),
                        IF(gus.Repl_client_priv='Y','REPLICATION CLIENT',NULL),
                        IF(gus.Create_view_priv='Y','CREATE VIEW',NULL),
                        IF(gus.Show_view_priv='Y','SHOW VIEW',NULL),
                        IF(gus.Create_routine_priv='Y','CREATE ROUTINE',NULL),
                        IF(gus.Alter_routine_priv='Y','ALTER ROUTINE',NULL),
                        IF(gus.Create_user_priv='Y','CREATE USER',NULL),
                        IF(gus.Event_priv='Y','EVENT',NULL),
                        IF(gus.Trigger_priv='Y','TRIGGER',NULL),
                        IF(gus.Create_tablespace_priv='Y','CREATE TABLESPACE',NULL)
                    )
                )
            ),
            " ON *.* TO '",gus.User,"'@'",gus.Host,"' REQUIRE ",
            CASE gus.ssl_type
                WHEN 'ANY' THEN
                    "SSL "
                WHEN 'X509' THEN
                    "X509 "
                WHEN 'SPECIFIED' THEN
                    CONCAT_WS("AND ",
                        IF((LENGTH(gus.ssl_cipher)>0),CONCAT("CIPHER '",CONVERT(gus.ssl_cipher USING utf8),"' "),NULL),
                        IF((LENGTH(gus.x509_issuer)>0),CONCAT("ISSUER '",CONVERT(gus.ssl_cipher USING utf8),"' "),NULL),
                        IF((LENGTH(gus.x509_subject)>0),CONCAT("SUBJECT '",CONVERT(gus.ssl_cipher USING utf8),"' "),NULL)
                    )
                ELSE "NONE "
            END,
            "WITH ",
            IF(gus.Grant_priv='Y',"GRANT OPTION ",""),
            "MAX_QUERIES_PER_HOUR ",gus.max_questions," ",
            "MAX_CONNECTIONS_PER_HOUR ",gus.max_connections," ",
            "MAX_UPDATES_PER_HOUR ",gus.max_updates," ",
            "MAX_USER_CONNECTIONS ",gus.max_user_connections,
            ";"
        ) AS 'GRANT Statement (Reconstructed)'
    FROM mysql.user gus;

    mysqlユーザーを作成する方法

    「ユーザーの作成」手順は、Oracleに似ています。最も簡単な例は次のとおりです。

    CREATE user 'username'@'hostname' identified by 'password';
    GRANT privilege_name on *.* TO 'username'@'hostname';

    付与して1行で作成するオプション:

    GRANT privilege_name  ON *.* TO 'username'@'hostname' identified by 'password';

    MySQL8.0で削除されました。

    MySQLを起動および停止するにはどうすればよいですか?

    このサービスでMySQLを停止および開始できます。

    実際のコマンドは、Linuxディストリビューションとサービス名によって異なります。

    以下に、サービス名mysqldの例を示します。

    Ubuntu

    /etc/init.d/mysqld start 
    /etc/init.d/mysqld stop 
    /etc/init.d/mysqld restart

    RedHat / Centos

    service mysqld start 
    service mysqld stop 
    service mysqld restart
    systemctl start mysqld.service
    systemctl stop mysqld.service
    systemctl restart mysqld.service

    MySQLサーバー構成データはどこにありますか?

    構成はmy.cnfファイルに保存されます。

    バージョン8.0までは、再起動後に残る動的設定の変更には、my.cnfファイルの手動更新が必要でした。 Oracleのscope=bothと同様に、persistentオプションを使用して値を変更できます。

    mysql> SET PERSIST max_connections = 1000;
    mysql> SET @@PERSIST.max_connections = 1000;

    古いバージョンの場合:

    mysql> SET GLOBAL max_connections = 1000;
    $ vi /etc/mysql/my.cnf
    SET GLOBAL max_connections = 1000;

    MySQLをバックアップするにはどうすればよいですか?

    mysqlバックアップを実行する方法は2つあります。

    小規模なデータベースまたは小規模な選択的バックアップの場合は、mysqldumpコマンドを使用できます。

    mysqldumpを使用したデータベースバックアップ(論理バックアップ):

    mysqldump -uuser -p --databases db_name --routines --events --single-transaction | gzip > db_name_backup.sql.gz

    xtrabackup、mariabackup(ホットバイナリバックアップ)

    推奨される方法は、xtrabackupまたはmariabackupの外部ツールを使用して、ホットバイナリバックアップを実行することです。

    Oracleは、MySQLEnterpriseEditionと呼ばれる有料バージョンでホットバイナリバックアップを提供しています。

    mariabackup --user=root --password=PASSWORD --backup --target-dir=/u01/backups/

    他のサーバーへのストリームバックアップ

    優先ポート(この例では1984)の外部サーバーでリスナーを起動します

    nc -l 1984 | pigz -cd - | pv | xbstream -x -C /u01/backups

    バックアップを実行して外部ホストに転送する

    innobackupex --user=root --password=PASSWORD --stream=xbstream /var/tmp | pigz  | pv | nc external_host.com 1984

    ユーザー権限のコピー

    多くの場合、ユーザー権限をコピーして他のサーバーに転送する必要があります。

    これを行うための推奨される方法は、pt-show-grantsを使用することです。

    pt-show-grants > /u01/backups

    MySQLを復元するにはどうすればよいですか?

    論理バックアップの復元

    MySQLdumpはSQLファイルを作成します。SQLファイルはsourceコマンドで実行できます。

    実行のログファイルを保持するには、teeコマンドを使用します。

    mysql> tee dump.log
    mysql> source mysqldump.sql

    バイナリバックアップの復元(xtrabackup / mariabackup)

    バイナリバックアップからMySQLを復元するには、最初にファイルを復元してから、ログファイルを適用する必要があります。

    このプロセスを比較して、Oracleで復元およびリカバリできます。

    xtrabackup --copy-back --target-dir=/var/lib/data
    innobackupex --apply-log --use-memory=[values in MB or GB] /var/lib/data

    うまくいけば、これらのヒントは、基本的な管理タスクを実行する方法の概要を示しています。


    1. MySQLWorkbenchの代替-ClusterControlデータベースのユーザー管理

    2. SQL Serverで無効になっているすべてのCHECK制約を返す方法(T-SQLの例)

    3. ブラウザなしでスタンドアロンとしてOracleFormsを実行する

    4. NOT IN vs NOT EXISTS