在将数据插入依赖于FOREIGN KEY的MySQL表中时,是否有必要每次都执行附加的SELECT和INSERT IGNORE查询?

Cha*_*kka 7 php mysql foreign-keys

我有4个MySQL表,通过FOREIGN KEY相互依赖

请检查以下表结构的架构:

CREATE DATABASE IF NOT EXISTS courses
    CHARACTER SET latin1
    COLLATE latin1_bin;

CREATE TABLE IF NOT EXISTS courses.institution
(
    icode INT UNSIGNED NOT NULL AUTO_INCREMENT,
    iname VARCHAR(255) NOT NULL,
    PRIMARY KEY (icode),
    UNIQUE (iname)
)
    ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS courses.cities
(
    ccode INT UNSIGNED NOT NULL AUTO_INCREMENT,
    cname VARCHAR(255) NOT NULL,
    PRIMARY KEY (ccode),
    UNIQUE (cname)
)
    ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS courses.skills
(
    scode INT UNSIGNED NOT NULL AUTO_INCREMENT,
    sname VARCHAR(255) NOT NULL,
    PRIMARY KEY (scode),
    UNIQUE (sname)
)
    ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS courses.relation
(
    icode INT UNSIGNED NOT NULL,
    scode INT UNSIGNED NOT NULL,
    ccode INT UNSIGNED NOT NULL,
    UNIQUE KEY ucols (icode, scode, ccode),
    FOREIGN KEY (icode) REFERENCES courses.institution (icode),
    FOREIGN KEY (scode) REFERENCES courses.skills (scode),
    FOREIGN KEY (ccode) REFERENCES courses.cities (ccode)
)
    ENGINE = InnoDB;
Run Code Online (Sandbox Code Playgroud)

目前,我正在执行以下查询,以便仅在关系表中插入一条记录。

每次仅插入一次,将花费4个INSERT查询和3个SELECT子查询。

INSERT IGNORE INTO institution(iname) VALUES ('ABC Learners');
INSERT IGNORE INTO skills(sname) VALUES ('PHP');
INSERT IGNORE INTO cities(cname) VALUES ('Bangalore');

INSERT IGNORE INTO relation (icode, scode, ccode) VALUES (
    (SELECT icode FROM institution WHERE iname = 'ABC Learners'),
    (SELECT scode FROM skills WHERE sname = 'PHP'),
    (SELECT ccode FROM cities WHERE cname = 'Bangalore')
);
Run Code Online (Sandbox Code Playgroud)

是否有必要每次都执行所有这些查询?还是有更好的方法在单个或几个查询中执行此操作?

在下面的简单PHP代码中签出。在此代码中,仅在关系表上插入7条记录,它在每个记录上执行4个INSERT查询,并带有3个SELECT子查询。

它花费7 * 7 = 49了7条记录的总查询。如何解决呢?

<?php

$db = new mysqli('localhost', 'user', '****', 'courses');

$records = [
    ['ABC Learners', 'CSS', 'Bangalore'],
    ['ABC Learners', 'PHP', 'Bangalore'],
    ['ABC Learners', 'HTML', 'Bangalore'],
    ['ABC Learners', 'PHP', 'Hyderabad'],
    ['XYZ Solutions', 'PHP', 'Hyderabad'],
    ['XYZ Solutions', 'JAVA', 'Hyderabad'],
    ['XYZ Solutions', 'JAVA', 'Bangalore'],
];

foreach ($records as $record) {

    list($institute, $skill, $city) = $record;

    $db->query("INSERT IGNORE INTO institution (iname) VALUES ('{$institute}')");
    $db->query("INSERT IGNORE INTO skills (sname) VALUES ('{$skill}')");
    $db->query("INSERT IGNORE INTO cities (cname) VALUES ('{$city}')");

    $db->query(
        "INSERT IGNORE INTO relation (icode, scode, ccode) VALUES (" .
        "(SELECT icode FROM institution WHERE iname = '{$institute}'), " .
        "(SELECT scode FROM skills WHERE sname = '{$skill}'), " .
        "(SELECT ccode FROM cities WHERE cname = '{$city}'))"
    );
}

$db->close();
Run Code Online (Sandbox Code Playgroud)

注意:上面的脚本是示例目的。使用批处理模式禁用自动提交对我没有用。因为很多时候我需要向关系表中添加一条新记录(基于通过Web面板的用户请求)


更新1:

经过一些研究和基准测试之后,我创建了一个MySQL存储函数来加速此过程,并将性能提高了250-300%

请在此处结帐功能:

DELIMITER $$
CREATE FUNCTION courses.record(i_name VARCHAR(255), s_name VARCHAR(255), c_name VARCHAR(255)) RETURNS INT
BEGIN
    DECLARE _icode, _scode, _ccode INT UNSIGNED;

    SELECT icode INTO _icode FROM institution WHERE iname = i_name;
    SELECT scode INTO _scode FROM skills WHERE sname = s_name;
    SELECT ccode INTO _ccode FROM cities WHERE cname = c_name;

    IF _icode IS NULL THEN
        INSERT IGNORE INTO institution (iname) VALUES (i_name);
        SELECT icode INTO _icode FROM institution WHERE iname = i_name;
    END IF;

    IF _scode IS NULL THEN
        INSERT IGNORE INTO skills (sname) VALUES (s_name);
        SELECT scode INTO _scode FROM skills WHERE sname = s_name;
    END IF;

    IF _ccode IS NULL THEN
        INSERT IGNORE INTO cities (cname) VALUES (c_name);
        SELECT ccode INTO _ccode FROM cities WHERE cname = c_name;
    END IF;

    INSERT IGNORE INTO relation (icode, scode, ccode) VALUES (_icode, _scode, _ccode);

    RETURN ROW_COUNT();
END $$
DELIMITER ;
Run Code Online (Sandbox Code Playgroud)

现在,这个下面的PHP脚本只需一个查询就可以在关系表中插入一条记录

<?php

$db = new mysqli('localhost', 'user', '***', 'courses');

$records = [
    ['ABC Learners', 'CSS', 'Bangalore'],
    ['ABC Learners', 'PHP', 'Bangalore'],
    ['ABC Learners', 'HTML', 'Bangalore'],
    ['ABC Learners', 'PHP', 'Hyderabad'],
    ['XYZ Solutions', 'PHP', 'Hyderabad'],
    ['XYZ Solutions', 'JAVA', 'Hyderabad'],
    ['XYZ Solutions', 'JAVA', 'Bangalore'],
];

$query = $db->prepare("SELECT record (?, ?, ?)");
$query->bind_param('sss', $institute, $skill, $city);

foreach ($records as $record) {
    list($institute, $skill, $city) = $record;
    $query->execute();
}

$db->close();
Run Code Online (Sandbox Code Playgroud)

该MySQL存储函数提高了性能。但是,我仍然在此函数中使用多个INSERTSELECT语句。是否可以使用少量语句来优化此功能以获得更高的性能?

Mad*_*iya 6

1.将UNIQUE更改为PRIMARY键

在没有定义显式主键的情况下,InnoDB将为每一行创建自己的隐藏主键。但是在您的情况下,在表中relation,当前UNIQUE键是更好的候选者,可以将其定义为PRIMARY键(NOT NULL并且满足“唯一性”)。因此将其PRIMARY KEY改为。

2.利用批量插入/选择

通过使用批处理插入和选择,并利用一些应用程序捕获,我们基本上可以将其整体分为4个插入和3个选择。检查下面的代码(带注释):

$records = [
    ['ABC Learners', 'CSS', 'Bangalore'],
    ['ABC Learners', 'PHP', 'Bangalore'],
    ['ABC Learners', 'HTML', 'Bangalore'],
    ['ABC Learners', 'PHP', 'Hyderabad'],
    ['XYZ Solutions', 'PHP', 'Hyderabad'],
    ['XYZ Solutions', 'JAVA', 'Hyderabad'],
    ['XYZ Solutions', 'JAVA', 'Bangalore'],
];

// Create a copy of records to avoid changing the original
$records_copy = $records;

// Get unique institutions, skills and cities
$i = array_unique(array_map('array_shift', $records_copy)); 
$s = array_unique(array_map('array_shift', $records_copy)); 
$c = array_unique(array_map('array_shift', $records_copy)); 

// Prepare batch insert and select SQL for institution table
$i_ins_sql = "INSERT IGNORE INTO institution (iname) VALUES ";
$i_sel_sql = "SELECT icode, iname FROM institution WHERE iname IN (";

foreach ($i as $v) {

    $i_ins_sql .= "('" . $db->real_escape_string($v) . "'),";
    $i_sel_sql .= "'" . $db->real_escape_string($v) . "',";
}

// Execute the INSERT query
$db->query( rtrim($i_ins_sql, ',') );

// Execute the SELECT query and fetch the query result and store (cache) it
// Key of the cache array would be name (string) and the value would be 
// the code (integer)
$i_cache = array();
$i_sel_q = $db->query( rtrim($i_sel_sql, ',') . ")" );
while ( $row = $i_sel_q->fetch_assoc() ) {
    $i_cache[$row['iname']] = $row['icode'];
}

/**
 * REPEAT the INSERT and SELECT steps for the skills and cities tables, 
 * using $s and $c arrays respectively, with appropriate field names
 * Eventually, you would have cache arrays, $s_cache and $c_cache.
 **/

// Now let's do a batch INSERT INTO relation table 
$sql = "INSERT IGNORE INTO relation (icode, scode, ccode) VALUES ";

// Loop over original records array
foreach ($records as $record) {

    $sql .= "(" . (int)$i_cache[$record[0]] . ","
                . (int)$i_cache[$record[1]] . "," 
                . (int)$i_cache[$record[2]] . "),";
}

// Execute the Batch INSERT query into relation table
$db->query( rtrim($sql, ',') );
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的一点:您的代码易于受到与SQL注入相关的攻击。甚至real_escape_string无法完全保护它。请学习改为使用预备语句


Nee*_*hod 0

如果您在插入时已经有了外键的 ID,则可以克服它,在客户端中您需要显示institutionandcities和 的列表skills,其中所有都包含id以及name其他相关信息,因此当用户选择特定的 时institutioncitiesskills 需要将相应的 ID 添加到您的请求数据中。前任

const { institutionId, skillId, cityId } = req.body;
Run Code Online (Sandbox Code Playgroud)

所以你只需要执行一个insert查询;

INSERT IGNORE INTO relation (icode, scode, ccode) VALUES ( 
institutionId, skillId, cityId);
Run Code Online (Sandbox Code Playgroud)

哪个更快。