ເປັນຫຍັງການສອບຖາມຂອງຂ້ອຍຈຶ່ງເຮັດໃຫ້ເກີດຄວາມຂັດຂ້ອງ?

ຂ້ອຍພະຍາຍາມເອົາບາງເອກະສານເຂົ້າໃນຖານຂໍ້ມູນ Postgres. ເນື່ອງຈາກວ່າມີການຊໍ້າຊ້ອນຫຼາຍຢ່າງ, ພວກເຮົາຈຶ່ງເອົາເອກະສານເຂົ້າໄປໃນຕາຕະລາງ ຂອງໄຟລ໌ , ຈາກນັ້ນເຊື່ອມຕໍ່ກັບພາກສ່ວນຂອງຖານຂໍ້ມູນທີ່ພວກເຮົາ ກຳ ລັງໃຊ້ກັບຕາຕະລາງ output_file . ຍ້ອນວ່າຕາຕະລາງເອກະສານ ຍັງຖືກອ້າງອີງໃສ່ຕາຕະລາງນອກ ເໜືອ ຈາກ output_file (ຕົວຢ່າງ, ຕາຕະລາງ ທີ່ຄ້າຍຄືກັນ [ຕາຕະລາງ ທີ່ຄ້າຍຄືກັນ), ໜຶ່ງ ໃນຖັນຂອງມັນແມ່ນຕົວເລກອ້າງອີງ, ເຊິ່ງ ມີການປັບປຸງດ້ວຍລະຫັດເມື່ອແຖວຖືກປ້ອນເຂົ້າໃນ output_file (ແລະຕາຕະລາງອື່ນໆກໍ່ຄືກັນ, ເຖິງແມ່ນວ່າມັນບໍ່ຖືກ ນຳ ໃຊ້ໃນຊ່ວງເວລາທີ່ເກີດບັນຫາ).

CREATE TABLE file
(
    file_id serial PRIMARY KEY,
    --other columns
    occurences integer NOT NULL DEFAULT 0
);

CREATE TABLE output_file
(
    output_file_id serial PRIMARY KEY,
    --other columns
    file_id integer REFERENCES file NOT NULL
);

CREATE OR REPLACE FUNCTION file_insert() RETURNS opaque AS '
    BEGIN
        UPDATE file
        SET occurences = occurences + 1
        WHERE file.file_id = NEW.file_id;

        RETURN NEW;
    END;
' LANGUAGE plpgsql;

CREATE TRIGGER output_file_insert AFTER INSERT
    ON output_file FOR EACH ROW
    EXECUTE PROCEDURE file_insert();

ລະຫັດທີ່ໃສ່ເອກະສານແມ່ນ

private void insertFiles(Set<File> files){
    SortedSet<Integer> outputFileIDs = new TreeSet<Integer>();
    PreparedStatement fileExistsStatement = getFileExistsStatement();
    for(File file : files) {
        try {
            int fileID = -1;

            ResultSet rs = /* Query to see if file already present in file table */

            if(rs.next()) {
                // File found
                fileID = rs.getInt(1);
            }

            if(fileID < 0) {
                /* File does not exist, upload it */

                rs = /* Query to get file ID */
                fileID = rs.getInt(1);
            }

            outputFileIDs.add(fileID);
        }
        catch(FileNotFoundException e){
            /* handle errors */
        }
    }

    Iterator<Integer> it = outputFileIDs.iterator();
    while(it.hasNext()){
        /* Insert reference in output file table */
        PreparedStatement outputFileStatement = "INSERT INTO output_file (file_id, /*...*/) VALUES (?, /*...*/);";
        outputFileStatement.setInt(1, it.next());
        outputFileStatement.executeUpdate();
    }
}

ບັນຫາຂອງຂ້ອຍແມ່ນວ່າລະຫັດລົ້ມເຫຼວຂອງລະຫັດ (ຂໍ້ຍົກເວັ້ນທີ່ສະແດງຢູ່ຂ້າງລຸ່ມ) ຫຼາຍ . ມັນຈະ chunter ຕາມ ລຳ ດັບຢ່າງມີຄວາມສຸກເປັນເວລາ ໜຶ່ງ, ຫຼັງຈາກນັ້ນ, ບັນດາຄວາມຫຍຸ້ງຍາກຈະເລີ່ມເກີດຂື້ນຢູ່ທົ່ວທຸກບ່ອນ, ໃນຂອບເຂດທີ່ບໍ່ມີຫຍັງເຮັດໃຫ້ມັນເຂົ້າໄປໃນຖານຂໍ້ມູນຕະຫຼອດເວລາທີ່ພວກເຮົາມ້ວນແລະທົດລອງ ໃໝ່. ຂ້ອຍມີຄວາມລະແວງສົງໄສວ່າເປັນຫຍັງມັນເປັນສິ່ງຄ້າງຄາຢູ່ໃນອັນດັບ ທຳ ອິດ, ເຖິງແມ່ນວ່າ. ບັດປະ ຈຳ ຕົວຂອງເອກະສານຖືກເກັບໄວ້ເປັນຊຸດທີ່ຈັດຮຽງກັນ, ແລະດັ່ງນັ້ນກະແຈຢູ່ໃນຕາຕະລາງ ເອກະສານ ຄວນໄດ້ຮັບຢ່າງເປັນລະບຽບຮຽບຮ້ອຍ ສຳ ລັບການເຮັດທຸລະ ກຳ ທຸກຢ່າງ, ຕາມທີ່ແນະ ນຳ ໃນປື້ມຄູ່ມືຂອງ Postgres, ປ້ອງກັນບໍ່ໃຫ້ເກີດຄວາມຂັດຂ້ອງ. ຂ້ອຍເຮັດຫຍັງຜິດ? Postgres ເຮັດວຽກຂອງມັນຕາມ ລຳ ດັບທີ່ບໍ່ໄດ້ ກຳ ນົດບໍ?

org.postgresql.util.PSQLException: ERROR: deadlock detected
    Detail: Process 8949 waits for ShareLock on transaction 256629; blocked by process 8924.
Process 8924 waits for ExclusiveLock on tuple (4148,40) of relation 30265 of database 16384; blocked by process 8949.
    Hint: See server log for query details.
    Where: SQL statement "UPDATE file
            SET occurences = occurences + 1
            WHERE file.file_id = NEW.file_id"
PL/pgSQL function "file_insert" line 2 at SQL statement
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2102)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1835)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:500)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:388)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:334)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
    at [outputFileStatement.executeUpdate(), above]

[ແກ້ໄຂ] ຕາມການຮ້ອງຂໍຂອງ axtavt, ການໂອນເງິນແມ່ນຖືກຈັດການໂດຍລະຫັດທີ່ເອີ້ນວ່າວິທີການທີ່ໄດ້ສະແດງ.

public void run(){
    /* connection.setAutoCommit(false) has already been called elsewhere */
    try{
        boolean committed = false;
        boolean deadlocked = false;

        synchronized(connection){
            do{
                deadlocked = false;
                try {
                    /* insert lots of other stuff */

                    if(!files.isEmpty()){
                        insertFiles(files);
                    }

                    /* insert some more stuff */

                    connection.commit();
                    committed = true;

                    closeStatements();
                }
                catch(PSQLException e){
                    if(e.getSQLState() != null){
                        if(e.getSQLState().equals("40P01")){
                            /* Log the fact that we're deadlocked */
                            deadlocked = true;
                        }
                        else{
                            throw e;
                        }
                    }
                    else{
                        throw e;
                    }
                }
                finally {
                    try {
                        if(!committed) {
                            connection.rollback();
                        }
                    }
                    catch (SQLException e) {
                        /* Log exceptions */
                    }
                }
            }while(deadlocked);
        }

    }
    catch(Exception e){
        /* Log exceptions */
    }
    finally{
        try {
            connection.close();
        }
        catch (SQLException e) {
            /* Log exceptions */
        }
    }
}
5
задан Scott 27 July 2011 в 09:49
поделиться